/* * Copyright 2012 Google Inc. * * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include #include #include #include #include #include #include #include #include #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wshadow" #pragma GCC diagnostic ignored "-Wundef" #pragma GCC diagnostic ignored "-Wunused" #pragma GCC diagnostic ignored "-Wundefined-internal" #elif defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4100 4127 4189 4267 4389 4456 4457 4458 4459 4668 4701 4706 4800 4996 5046 6001 6011 6246 6282 6297 6323 6385 6386 28182) #endif #include "skia-simplify.h" #if defined(_WIN32) || defined(__SYMBIAN32__) #define WIN32_LEAN_AND_MEAN #define NOMINMAX #include #undef near #undef far #else #include #ifdef __APPLE__ #include #include #else #include #include #endif #endif #undef PI #define TArray SKIA_TArray #define Top SKIA_Top #ifdef SKIA_SIMPLIFY_NAMESPACE namespace SKIA_SIMPLIFY_NAMESPACE { #endif /** \enum SkAlphaType Describes how to interpret the alpha component of a pixel. A pixel may be opaque, or alpha, describing multiple levels of transparency. In simple blending, alpha weights the draw color and the destination color to create a new color. If alpha describes a weight from zero to one: new color = draw color * alpha + destination color * (1 - alpha) In practice alpha is encoded in two or more bits, where 1.0 equals all bits set. RGB may have alpha included in each component value; the stored value is the original RGB multiplied by alpha. Premultiplied color components improve performance. */ enum SkAlphaType : int { kUnknown_SkAlphaType, //!< uninitialized kOpaque_SkAlphaType, //!< pixel is opaque kPremul_SkAlphaType, //!< pixel components are premultiplied by alpha kUnpremul_SkAlphaType, //!< pixel components are independent of alpha kLastEnum_SkAlphaType = kUnpremul_SkAlphaType, //!< last valid value }; /** Returns true if SkAlphaType equals kOpaque_SkAlphaType. kOpaque_SkAlphaType is a hint that the SkColorType is opaque, or that all alpha values are set to their 1.0 equivalent. If SkAlphaType is kOpaque_SkAlphaType, and SkColorType is not opaque, then the result of drawing any pixel with a alpha value less than 1.0 is undefined. */ static inline bool SkAlphaTypeIsOpaque(SkAlphaType at) { return kOpaque_SkAlphaType == at; } /** \file SkColor.h Types, consts, functions, and macros for colors. */ /** 8-bit type for an alpha value. 255 is 100% opaque, zero is 100% transparent. */ typedef uint8_t SkAlpha; /** 32-bit ARGB color value, unpremultiplied. Color components are always in a known order. This is different from SkPMColor, which has its bytes in a configuration dependent order, to match the format of kBGRA_8888_SkColorType bitmaps. SkColor is the type used to specify colors in SkPaint and in gradients. Color that is premultiplied has the same component values as color that is unpremultiplied if alpha is 255, fully opaque, although may have the component values in a different order. */ typedef uint32_t SkColor; /** Returns color value from 8-bit component values. Asserts if SK_DEBUG is defined if a, r, g, or b exceed 255. Since color is unpremultiplied, a may be smaller than the largest of r, g, and b. @param a amount of alpha, from fully transparent (0) to fully opaque (255) @param r amount of red, from no red (0) to full red (255) @param g amount of green, from no green (0) to full green (255) @param b amount of blue, from no blue (0) to full blue (255) @return color and alpha, unpremultiplied */ static constexpr inline SkColor SkColorSetARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) { return SkASSERT(a <= 255 && r <= 255 && g <= 255 && b <= 255), (a << 24) | (r << 16) | (g << 8) | (b << 0); } /** Returns color value from 8-bit component values, with alpha set fully opaque to 255. */ #define SkColorSetRGB(r, g, b) SkColorSetARGB(0xFF, r, g, b) /** Returns alpha byte from color value. */ #define SkColorGetA(color) (((color) >> 24) & 0xFF) /** Returns red component of color, from zero to 255. */ #define SkColorGetR(color) (((color) >> 16) & 0xFF) /** Returns green component of color, from zero to 255. */ #define SkColorGetG(color) (((color) >> 8) & 0xFF) /** Returns blue component of color, from zero to 255. */ #define SkColorGetB(color) (((color) >> 0) & 0xFF) /** Returns unpremultiplied color with red, blue, and green set from c; and alpha set from a. Alpha component of c is ignored and is replaced by a in result. @param c packed RGB, eight bits per component @param a alpha: transparent at zero, fully opaque at 255 @return color with transparency */ [[nodiscard]] static constexpr inline SkColor SkColorSetA(SkColor c, U8CPU a) { return (c & 0x00FFFFFF) | (a << 24); } /** Represents fully transparent SkAlpha value. SkAlpha ranges from zero, fully transparent; to 255, fully opaque. */ constexpr SkAlpha SK_AlphaTRANSPARENT = 0x00; /** Represents fully opaque SkAlpha value. SkAlpha ranges from zero, fully transparent; to 255, fully opaque. */ constexpr SkAlpha SK_AlphaOPAQUE = 0xFF; /** Represents fully transparent SkColor. May be used to initialize a destination containing a mask or a non-rectangular image. */ constexpr SkColor SK_ColorTRANSPARENT = SkColorSetARGB(0x00, 0x00, 0x00, 0x00); /** Represents fully opaque black. */ constexpr SkColor SK_ColorBLACK = SkColorSetARGB(0xFF, 0x00, 0x00, 0x00); /** Represents fully opaque dark gray. Note that SVG dark gray is equivalent to 0xFFA9A9A9. */ constexpr SkColor SK_ColorDKGRAY = SkColorSetARGB(0xFF, 0x44, 0x44, 0x44); /** Represents fully opaque gray. Note that HTML gray is equivalent to 0xFF808080. */ constexpr SkColor SK_ColorGRAY = SkColorSetARGB(0xFF, 0x88, 0x88, 0x88); /** Represents fully opaque light gray. HTML silver is equivalent to 0xFFC0C0C0. Note that SVG light gray is equivalent to 0xFFD3D3D3. */ constexpr SkColor SK_ColorLTGRAY = SkColorSetARGB(0xFF, 0xCC, 0xCC, 0xCC); /** Represents fully opaque white. */ constexpr SkColor SK_ColorWHITE = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0xFF); /** Represents fully opaque red. */ constexpr SkColor SK_ColorRED = SkColorSetARGB(0xFF, 0xFF, 0x00, 0x00); /** Represents fully opaque green. HTML lime is equivalent. Note that HTML green is equivalent to 0xFF008000. */ constexpr SkColor SK_ColorGREEN = SkColorSetARGB(0xFF, 0x00, 0xFF, 0x00); /** Represents fully opaque blue. */ constexpr SkColor SK_ColorBLUE = SkColorSetARGB(0xFF, 0x00, 0x00, 0xFF); /** Represents fully opaque yellow. */ constexpr SkColor SK_ColorYELLOW = SkColorSetARGB(0xFF, 0xFF, 0xFF, 0x00); /** Represents fully opaque cyan. HTML aqua is equivalent. */ constexpr SkColor SK_ColorCYAN = SkColorSetARGB(0xFF, 0x00, 0xFF, 0xFF); /** Represents fully opaque magenta. HTML fuchsia is equivalent. */ constexpr SkColor SK_ColorMAGENTA = SkColorSetARGB(0xFF, 0xFF, 0x00, 0xFF); /** Converts RGB to its HSV components. hsv[0] contains hsv hue, a value from zero to less than 360. hsv[1] contains hsv saturation, a value from zero to one. hsv[2] contains hsv value, a value from zero to one. @param red red component value from zero to 255 @param green green component value from zero to 255 @param blue blue component value from zero to 255 @param hsv three element array which holds the resulting HSV components */ SK_API void SkRGBToHSV(U8CPU red, U8CPU green, U8CPU blue, SkScalar hsv[3]); /** Converts ARGB to its HSV components. Alpha in ARGB is ignored. hsv[0] contains hsv hue, and is assigned a value from zero to less than 360. hsv[1] contains hsv saturation, a value from zero to one. hsv[2] contains hsv value, a value from zero to one. @param color ARGB color to convert @param hsv three element array which holds the resulting HSV components */ static inline void SkColorToHSV(SkColor color, SkScalar hsv[3]) { SkRGBToHSV(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color), hsv); } /** Converts HSV components to an ARGB color. Alpha is passed through unchanged. hsv[0] represents hsv hue, an angle from zero to less than 360. hsv[1] represents hsv saturation, and varies from zero to one. hsv[2] represents hsv value, and varies from zero to one. Out of range hsv values are pinned. @param alpha alpha component of the returned ARGB color @param hsv three element array which holds the input HSV components @return ARGB equivalent to HSV */ SK_API SkColor SkHSVToColor(U8CPU alpha, const SkScalar hsv[3]); /** Converts HSV components to an ARGB color. Alpha is set to 255. hsv[0] represents hsv hue, an angle from zero to less than 360. hsv[1] represents hsv saturation, and varies from zero to one. hsv[2] represents hsv value, and varies from zero to one. Out of range hsv values are pinned. @param hsv three element array which holds the input HSV components @return RGB equivalent to HSV */ static inline SkColor SkHSVToColor(const SkScalar hsv[3]) { return SkHSVToColor(0xFF, hsv); } /** 32-bit ARGB color value, premultiplied. The byte order for this value is configuration dependent, matching the format of kBGRA_8888_SkColorType bitmaps. This is different from SkColor, which is unpremultiplied, and is always in the same byte order. */ typedef uint32_t SkPMColor; /** Returns a SkPMColor value from unpremultiplied 8-bit component values. @param a amount of alpha, from fully transparent (0) to fully opaque (255) @param r amount of red, from no red (0) to full red (255) @param g amount of green, from no green (0) to full green (255) @param b amount of blue, from no blue (0) to full blue (255) @return premultiplied color */ SK_API SkPMColor SkPreMultiplyARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b); /** Returns pmcolor closest to color c. Multiplies c RGB components by the c alpha, and arranges the bytes to match the format of kN32_SkColorType. @param c unpremultiplied ARGB color @return premultiplied color */ SK_API SkPMColor SkPreMultiplyColor(SkColor c); /** \enum SkColorChannel Describes different color channels one can manipulate */ enum class SkColorChannel { kR, // the red channel kG, // the green channel kB, // the blue channel kA, // the alpha channel kLastEnum = kA, }; /** Used to represent the channels available in a color type or texture format as a mask. */ enum SkColorChannelFlag : uint32_t { kRed_SkColorChannelFlag = 1 << static_cast(SkColorChannel::kR), kGreen_SkColorChannelFlag = 1 << static_cast(SkColorChannel::kG), kBlue_SkColorChannelFlag = 1 << static_cast(SkColorChannel::kB), kAlpha_SkColorChannelFlag = 1 << static_cast(SkColorChannel::kA), kGray_SkColorChannelFlag = 0x10, // Convenience values kGrayAlpha_SkColorChannelFlags = kGray_SkColorChannelFlag | kAlpha_SkColorChannelFlag, kRG_SkColorChannelFlags = kRed_SkColorChannelFlag | kGreen_SkColorChannelFlag, kRGB_SkColorChannelFlags = kRG_SkColorChannelFlags | kBlue_SkColorChannelFlag, kRGBA_SkColorChannelFlags = kRGB_SkColorChannelFlags | kAlpha_SkColorChannelFlag, }; static_assert(0 == (kGray_SkColorChannelFlag & kRGBA_SkColorChannelFlags), "bitfield conflict"); /** \struct SkRGBA4f RGBA color value, holding four floating point components. Color components are always in a known order. kAT determines if the SkRGBA4f's R, G, and B components are premultiplied by alpha or not. Skia's public API always uses unpremultiplied colors, which can be stored as SkRGBA4f. For convenience, this type can also be referred to as SkColor4f. */ template struct SkRGBA4f { float fR; //!< red component float fG; //!< green component float fB; //!< blue component float fA; //!< alpha component /** Compares SkRGBA4f with other, and returns true if all components are equal. @param other SkRGBA4f to compare @return true if SkRGBA4f equals other */ bool operator==(const SkRGBA4f& other) const { return fA == other.fA && fR == other.fR && fG == other.fG && fB == other.fB; } /** Compares SkRGBA4f with other, and returns true if not all components are equal. @param other SkRGBA4f to compare @return true if SkRGBA4f is not equal to other */ bool operator!=(const SkRGBA4f& other) const { return !(*this == other); } /** Returns SkRGBA4f multiplied by scale. @param scale value to multiply by @return SkRGBA4f as (fR * scale, fG * scale, fB * scale, fA * scale) */ SkRGBA4f operator*(float scale) const { return { fR * scale, fG * scale, fB * scale, fA * scale }; } /** Returns SkRGBA4f multiplied component-wise by scale. @param scale SkRGBA4f to multiply by @return SkRGBA4f as (fR * scale.fR, fG * scale.fG, fB * scale.fB, fA * scale.fA) */ SkRGBA4f operator*(const SkRGBA4f& scale) const { return { fR * scale.fR, fG * scale.fG, fB * scale.fB, fA * scale.fA }; } /** Returns a pointer to components of SkRGBA4f, for array access. @return pointer to array [fR, fG, fB, fA] */ const float* vec() const { return &fR; } /** Returns a pointer to components of SkRGBA4f, for array access. @return pointer to array [fR, fG, fB, fA] */ float* vec() { return &fR; } /** As a std::array */ std::array array() const { return {fR, fG, fB, fA}; } /** Returns one component. Asserts if index is out of range and SK_DEBUG is defined. @param index one of: 0 (fR), 1 (fG), 2 (fB), 3 (fA) @return value corresponding to index */ float operator[](int index) const { SkASSERT(index >= 0 && index < 4); return this->vec()[index]; } /** Returns one component. Asserts if index is out of range and SK_DEBUG is defined. @param index one of: 0 (fR), 1 (fG), 2 (fB), 3 (fA) @return value corresponding to index */ float& operator[](int index) { SkASSERT(index >= 0 && index < 4); return this->vec()[index]; } /** Returns true if SkRGBA4f is an opaque color. Asserts if fA is out of range and SK_DEBUG is defined. @return true if SkRGBA4f is opaque */ bool isOpaque() const { SkASSERT(fA <= 1.0f && fA >= 0.0f); return fA == 1.0f; } /** Returns true if all channels are in [0, 1]. */ bool fitsInBytes() const { SkASSERT(fA >= 0.0f && fA <= 1.0f); return fR >= 0.0f && fR <= 1.0f && fG >= 0.0f && fG <= 1.0f && fB >= 0.0f && fB <= 1.0f; } /** Returns closest SkRGBA4f to SkColor. Only allowed if SkRGBA4f is unpremultiplied. @param color Color with Alpha, red, blue, and green components @return SkColor as SkRGBA4f example: https://fiddle.skia.org/c/@RGBA4f_FromColor */ static SkRGBA4f FromColor(SkColor color); // impl. depends on kAT /** Returns closest SkColor to SkRGBA4f. Only allowed if SkRGBA4f is unpremultiplied. @return color as SkColor example: https://fiddle.skia.org/c/@RGBA4f_toSkColor */ SkColor toSkColor() const; // impl. depends on kAT /** Returns closest SkRGBA4f to SkPMColor. Only allowed if SkRGBA4f is premultiplied. @return SkPMColor as SkRGBA4f */ static SkRGBA4f FromPMColor(SkPMColor); // impl. depends on kAT /** Returns SkRGBA4f premultiplied by alpha. Asserts at compile time if SkRGBA4f is already premultiplied. @return premultiplied color */ SkRGBA4f premul() const { static_assert(kAT == kUnpremul_SkAlphaType, ""); return { fR * fA, fG * fA, fB * fA, fA }; } /** Returns SkRGBA4f unpremultiplied by alpha. Asserts at compile time if SkRGBA4f is already unpremultiplied. @return unpremultiplied color */ SkRGBA4f unpremul() const { static_assert(kAT == kPremul_SkAlphaType, ""); if (fA == 0.0f) { return { 0, 0, 0, 0 }; } else { float invAlpha = 1 / fA; return { fR * invAlpha, fG * invAlpha, fB * invAlpha, fA }; } } // This produces bytes in RGBA order (eg GrColor). Impl. is the same, regardless of kAT uint32_t toBytes_RGBA() const; static SkRGBA4f FromBytes_RGBA(uint32_t color); /** Returns a copy of the SkRGBA4f but with alpha component set to 1.0f. @return opaque color */ SkRGBA4f makeOpaque() const { return { fR, fG, fB, 1.0f }; } }; /** \struct SkColor4f RGBA color value, holding four floating point components. Color components are always in a known order, and are unpremultiplied. This is a specialization of SkRGBA4f. For details, @see SkRGBA4f. */ using SkColor4f = SkRGBA4f; template <> SK_API SkColor4f SkColor4f::FromColor(SkColor); template <> SK_API SkColor SkColor4f::toSkColor() const; template <> SK_API uint32_t SkColor4f::toBytes_RGBA() const; template <> SK_API SkColor4f SkColor4f::FromBytes_RGBA(uint32_t color); namespace SkColors { constexpr SkColor4f kTransparent = {0, 0, 0, 0}; constexpr SkColor4f kBlack = {0, 0, 0, 1}; constexpr SkColor4f kDkGray = {0.25f, 0.25f, 0.25f, 1}; constexpr SkColor4f kGray = {0.50f, 0.50f, 0.50f, 1}; constexpr SkColor4f kLtGray = {0.75f, 0.75f, 0.75f, 1}; constexpr SkColor4f kWhite = {1, 1, 1, 1}; constexpr SkColor4f kRed = {1, 0, 0, 1}; constexpr SkColor4f kGreen = {0, 1, 0, 1}; constexpr SkColor4f kBlue = {0, 0, 1, 1}; constexpr SkColor4f kYellow = {1, 1, 0, 1}; constexpr SkColor4f kCyan = {0, 1, 1, 1}; constexpr SkColor4f kMagenta = {1, 0, 1, 1}; } // namespace SkColors /** \enum SkColorType Describes how pixel bits encode color. A pixel may be an alpha mask, a grayscale, RGB, or ARGB. kN32_SkColorType selects the native 32-bit ARGB format for the current configuration. This can lead to inconsistent results across platforms, so use with caution. */ enum SkColorType : int { kUnknown_SkColorType, //!< uninitialized kAlpha_8_SkColorType, //!< pixel with alpha in 8-bit byte kRGB_565_SkColorType, //!< pixel with 5 bits red, 6 bits green, 5 bits blue, in 16-bit word kARGB_4444_SkColorType, //!< pixel with 4 bits for alpha, red, green, blue; in 16-bit word kRGBA_8888_SkColorType, //!< pixel with 8 bits for red, green, blue, alpha; in 32-bit word kRGB_888x_SkColorType, //!< pixel with 8 bits each for red, green, blue; in 32-bit word kBGRA_8888_SkColorType, //!< pixel with 8 bits for blue, green, red, alpha; in 32-bit word kRGBA_1010102_SkColorType, //!< 10 bits for red, green, blue; 2 bits for alpha; in 32-bit word kBGRA_1010102_SkColorType, //!< 10 bits for blue, green, red; 2 bits for alpha; in 32-bit word kRGB_101010x_SkColorType, //!< pixel with 10 bits each for red, green, blue; in 32-bit word kBGR_101010x_SkColorType, //!< pixel with 10 bits each for blue, green, red; in 32-bit word kBGR_101010x_XR_SkColorType, //!< pixel with 10 bits each for blue, green, red; in 32-bit word, extended range kRGBA_10x6_SkColorType, //!< pixel with 10 used bits (most significant) followed by 6 unused // bits for red, green, blue, alpha; in 64-bit word kGray_8_SkColorType, //!< pixel with grayscale level in 8-bit byte kRGBA_F16Norm_SkColorType, //!< pixel with half floats in [0,1] for red, green, blue, alpha; // in 64-bit word kRGBA_F16_SkColorType, //!< pixel with half floats for red, green, blue, alpha; // in 64-bit word kRGBA_F32_SkColorType, //!< pixel using C float for red, green, blue, alpha; in 128-bit word // The following 6 colortypes are just for reading from - not for rendering to kR8G8_unorm_SkColorType, //!< pixel with a uint8_t for red and green kA16_float_SkColorType, //!< pixel with a half float for alpha kR16G16_float_SkColorType, //!< pixel with a half float for red and green kA16_unorm_SkColorType, //!< pixel with a little endian uint16_t for alpha kR16G16_unorm_SkColorType, //!< pixel with a little endian uint16_t for red and green kR16G16B16A16_unorm_SkColorType, //!< pixel with a little endian uint16_t for red, green, blue // and alpha kSRGBA_8888_SkColorType, kR8_unorm_SkColorType, kLastEnum_SkColorType = kR8_unorm_SkColorType, //!< last valid value #if SK_PMCOLOR_BYTE_ORDER(B,G,R,A) kN32_SkColorType = kBGRA_8888_SkColorType,//!< native 32-bit BGRA encoding #elif SK_PMCOLOR_BYTE_ORDER(R,G,B,A) kN32_SkColorType = kRGBA_8888_SkColorType,//!< native 32-bit RGBA encoding #else #error "SK_*32_SHIFT values must correspond to BGRA or RGBA byte order" #endif }; static constexpr int kSkColorTypeCnt = static_cast(kLastEnum_SkColorType) + 1; class SkColorSpace; /** Returns the number of bytes required to store a pixel, including unused padding. Returns zero if ct is kUnknown_SkColorType or invalid. @return bytes per pixel */ SK_API int SkColorTypeBytesPerPixel(SkColorType ct); /** Returns true if SkColorType always decodes alpha to 1.0, making the pixel fully opaque. If true, SkColorType does not reserve bits to encode alpha. @return true if alpha is always set to 1.0 */ SK_API bool SkColorTypeIsAlwaysOpaque(SkColorType ct); /** Returns true if canonical can be set to a valid SkAlphaType for colorType. If there is more than one valid canonical SkAlphaType, set to alphaType, if valid. If true is returned and canonical is not nullptr, store valid SkAlphaType. Returns false only if alphaType is kUnknown_SkAlphaType, color type is not kUnknown_SkColorType, and SkColorType is not always opaque. If false is returned, canonical is ignored. @param canonical storage for SkAlphaType @return true if valid SkAlphaType can be associated with colorType */ SK_API bool SkColorTypeValidateAlphaType(SkColorType colorType, SkAlphaType alphaType, SkAlphaType* canonical = nullptr); /** \enum SkImageInfo::SkYUVColorSpace Describes color range of YUV pixels. The color mapping from YUV to RGB varies depending on the source. YUV pixels may be generated by JPEG images, standard video streams, or high definition video streams. Each has its own mapping from YUV to RGB. JPEG YUV values encode the full range of 0 to 255 for all three components. Video YUV values often range from 16 to 235 for Y and from 16 to 240 for U and V (limited). Details of encoding and conversion to RGB are described in YCbCr color space. The identity colorspace exists to provide a utility mapping from Y to R, U to G and V to B. It can be used to visualize the YUV planes or to explicitly post process the YUV channels. */ enum SkYUVColorSpace : int { kJPEG_Full_SkYUVColorSpace, //!< describes full range kRec601_Limited_SkYUVColorSpace, //!< describes SDTV range kRec709_Full_SkYUVColorSpace, //!< describes HDTV range kRec709_Limited_SkYUVColorSpace, kBT2020_8bit_Full_SkYUVColorSpace, //!< describes UHDTV range, non-constant-luminance kBT2020_8bit_Limited_SkYUVColorSpace, kBT2020_10bit_Full_SkYUVColorSpace, kBT2020_10bit_Limited_SkYUVColorSpace, kBT2020_12bit_Full_SkYUVColorSpace, kBT2020_12bit_Limited_SkYUVColorSpace, kIdentity_SkYUVColorSpace, //!< maps Y->R, U->G, V->B kLastEnum_SkYUVColorSpace = kIdentity_SkYUVColorSpace, //!< last valid value // Legacy (deprecated) names: kJPEG_SkYUVColorSpace = kJPEG_Full_SkYUVColorSpace, kRec601_SkYUVColorSpace = kRec601_Limited_SkYUVColorSpace, kRec709_SkYUVColorSpace = kRec709_Limited_SkYUVColorSpace, kBT2020_SkYUVColorSpace = kBT2020_8bit_Limited_SkYUVColorSpace, }; /** \struct SkColorInfo Describes pixel and encoding. SkImageInfo can be created from SkColorInfo by providing dimensions. It encodes how pixel bits describe alpha, transparency; color components red, blue, and green; and SkColorSpace, the range and linearity of colors. */ class SK_API SkColorInfo { public: /** Creates an SkColorInfo with kUnknown_SkColorType, kUnknown_SkAlphaType, and no SkColorSpace. @return empty SkImageInfo */ SkColorInfo(); ~SkColorInfo(); /** Creates SkColorInfo from SkColorType ct, SkAlphaType at, and optionally SkColorSpace cs. If SkColorSpace cs is nullptr and SkColorInfo is part of drawing source: SkColorSpace defaults to sRGB, mapping into SkSurface SkColorSpace. Parameters are not validated to see if their values are legal, or that the combination is supported. @return created SkColorInfo */ SkColorInfo(SkColorType ct, SkAlphaType at, sk_sp cs); SkColorInfo(const SkColorInfo&); SkColorInfo(SkColorInfo&&); SkColorInfo& operator=(const SkColorInfo&); SkColorInfo& operator=(SkColorInfo&&); SkColorSpace* colorSpace() const; sk_sp refColorSpace() const; SkColorType colorType() const { return fColorType; } SkAlphaType alphaType() const { return fAlphaType; } bool isOpaque() const { return SkAlphaTypeIsOpaque(fAlphaType) || SkColorTypeIsAlwaysOpaque(fColorType); } bool gammaCloseToSRGB() const; /** Does other represent the same color type, alpha type, and color space? */ bool operator==(const SkColorInfo& other) const; /** Does other represent a different color type, alpha type, or color space? */ bool operator!=(const SkColorInfo& other) const; /** Creates SkColorInfo with same SkColorType, SkColorSpace, with SkAlphaType set to newAlphaType. Created SkColorInfo contains newAlphaType even if it is incompatible with SkColorType, in which case SkAlphaType in SkColorInfo is ignored. */ SkColorInfo makeAlphaType(SkAlphaType newAlphaType) const; /** Creates new SkColorInfo with same SkAlphaType, SkColorSpace, with SkColorType set to newColorType. */ SkColorInfo makeColorType(SkColorType newColorType) const; /** Creates SkColorInfo with same SkAlphaType, SkColorType, with SkColorSpace set to cs. cs may be nullptr. */ SkColorInfo makeColorSpace(sk_sp cs) const; /** Returns number of bytes per pixel required by SkColorType. Returns zero if colorType() is kUnknown_SkColorType. @return bytes in pixel example: https://fiddle.skia.org/c/@ImageInfo_bytesPerPixel */ int bytesPerPixel() const; /** Returns bit shift converting row bytes to row pixels. Returns zero for kUnknown_SkColorType. @return one of: 0, 1, 2, 3, 4; left shift to convert pixels to bytes example: https://fiddle.skia.org/c/@ImageInfo_shiftPerPixel */ int shiftPerPixel() const; private: sk_sp fColorSpace; SkColorType fColorType = kUnknown_SkColorType; SkAlphaType fAlphaType = kUnknown_SkAlphaType; }; /** \struct SkImageInfo Describes pixel dimensions and encoding. SkBitmap, SkImage, PixMap, and SkSurface can be created from SkImageInfo. SkImageInfo can be retrieved from SkBitmap and SkPixmap, but not from SkImage and SkSurface. For example, SkImage and SkSurface implementations may defer pixel depth, so may not completely specify SkImageInfo. SkImageInfo contains dimensions, the pixel integral width and height. It encodes how pixel bits describe alpha, transparency; color components red, blue, and green; and SkColorSpace, the range and linearity of colors. */ struct SK_API SkImageInfo { public: /** Creates an empty SkImageInfo with kUnknown_SkColorType, kUnknown_SkAlphaType, a width and height of zero, and no SkColorSpace. @return empty SkImageInfo */ SkImageInfo() = default; /** Creates SkImageInfo from integral dimensions width and height, SkColorType ct, SkAlphaType at, and optionally SkColorSpace cs. If SkColorSpace cs is nullptr and SkImageInfo is part of drawing source: SkColorSpace defaults to sRGB, mapping into SkSurface SkColorSpace. Parameters are not validated to see if their values are legal, or that the combination is supported. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @param cs range of colors; may be nullptr @return created SkImageInfo */ static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at); static SkImageInfo Make(int width, int height, SkColorType ct, SkAlphaType at, sk_sp cs); static SkImageInfo Make(SkISize dimensions, SkColorType ct, SkAlphaType at); static SkImageInfo Make(SkISize dimensions, SkColorType ct, SkAlphaType at, sk_sp cs); /** Creates SkImageInfo from integral dimensions and SkColorInfo colorInfo, Parameters are not validated to see if their values are legal, or that the combination is supported. @param dimensions pixel column and row count; must be zeros or greater @param SkColorInfo the pixel encoding consisting of SkColorType, SkAlphaType, and SkColorSpace (which may be nullptr) @return created SkImageInfo */ static SkImageInfo Make(SkISize dimensions, const SkColorInfo& colorInfo) { return SkImageInfo(dimensions, colorInfo); } static SkImageInfo Make(SkISize dimensions, SkColorInfo&& colorInfo) { return SkImageInfo(dimensions, std::move(colorInfo)); } /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, SkAlphaType at, and optionally SkColorSpace cs. kN32_SkColorType will equal either kBGRA_8888_SkColorType or kRGBA_8888_SkColorType, whichever is optimal. If SkColorSpace cs is nullptr and SkImageInfo is part of drawing source: SkColorSpace defaults to sRGB, mapping into SkSurface SkColorSpace. Parameters are not validated to see if their values are legal, or that the combination is supported. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @param cs range of colors; may be nullptr @return created SkImageInfo */ static SkImageInfo MakeN32(int width, int height, SkAlphaType at); static SkImageInfo MakeN32(int width, int height, SkAlphaType at, sk_sp cs); /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, SkAlphaType at, with sRGB SkColorSpace. Parameters are not validated to see if their values are legal, or that the combination is supported. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @return created SkImageInfo example: https://fiddle.skia.org/c/@ImageInfo_MakeS32 */ static SkImageInfo MakeS32(int width, int height, SkAlphaType at); /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, kPremul_SkAlphaType, with optional SkColorSpace. If SkColorSpace cs is nullptr and SkImageInfo is part of drawing source: SkColorSpace defaults to sRGB, mapping into SkSurface SkColorSpace. Parameters are not validated to see if their values are legal, or that the combination is supported. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @param cs range of colors; may be nullptr @return created SkImageInfo */ static SkImageInfo MakeN32Premul(int width, int height); static SkImageInfo MakeN32Premul(int width, int height, sk_sp cs); /** Creates SkImageInfo from integral dimensions width and height, kN32_SkColorType, kPremul_SkAlphaType, with SkColorSpace set to nullptr. If SkImageInfo is part of drawing source: SkColorSpace defaults to sRGB, mapping into SkSurface SkColorSpace. Parameters are not validated to see if their values are legal, or that the combination is supported. @param dimensions width and height, each must be zero or greater @param cs range of colors; may be nullptr @return created SkImageInfo */ static SkImageInfo MakeN32Premul(SkISize dimensions); static SkImageInfo MakeN32Premul(SkISize dimensions, sk_sp cs); /** Creates SkImageInfo from integral dimensions width and height, kAlpha_8_SkColorType, kPremul_SkAlphaType, with SkColorSpace set to nullptr. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @return created SkImageInfo */ static SkImageInfo MakeA8(int width, int height); /** Creates SkImageInfo from integral dimensions, kAlpha_8_SkColorType, kPremul_SkAlphaType, with SkColorSpace set to nullptr. @param dimensions pixel row and column count; must be zero or greater @return created SkImageInfo */ static SkImageInfo MakeA8(SkISize dimensions); /** Creates SkImageInfo from integral dimensions width and height, kUnknown_SkColorType, kUnknown_SkAlphaType, with SkColorSpace set to nullptr. Returned SkImageInfo as part of source does not draw, and as part of destination can not be drawn to. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @return created SkImageInfo */ static SkImageInfo MakeUnknown(int width, int height); /** Creates SkImageInfo from integral dimensions width and height set to zero, kUnknown_SkColorType, kUnknown_SkAlphaType, with SkColorSpace set to nullptr. Returned SkImageInfo as part of source does not draw, and as part of destination can not be drawn to. @return created SkImageInfo */ static SkImageInfo MakeUnknown() { return MakeUnknown(0, 0); } /** Returns pixel count in each row. @return pixel width */ int width() const { return fDimensions.width(); } /** Returns pixel row count. @return pixel height */ int height() const { return fDimensions.height(); } SkColorType colorType() const { return fColorInfo.colorType(); } SkAlphaType alphaType() const { return fColorInfo.alphaType(); } /** Returns SkColorSpace, the range of colors. The reference count of SkColorSpace is unchanged. The returned SkColorSpace is immutable. @return SkColorSpace, or nullptr */ SkColorSpace* colorSpace() const; /** Returns smart pointer to SkColorSpace, the range of colors. The smart pointer tracks the number of objects sharing this SkColorSpace reference so the memory is released when the owners destruct. The returned SkColorSpace is immutable. @return SkColorSpace wrapped in a smart pointer */ sk_sp refColorSpace() const; /** Returns if SkImageInfo describes an empty area of pixels by checking if either width or height is zero or smaller. @return true if either dimension is zero or smaller */ bool isEmpty() const { return fDimensions.isEmpty(); } /** Returns the dimensionless SkColorInfo that represents the same color type, alpha type, and color space as this SkImageInfo. */ const SkColorInfo& colorInfo() const { return fColorInfo; } /** Returns true if SkAlphaType is set to hint that all pixels are opaque; their alpha value is implicitly or explicitly 1.0. If true, and all pixels are not opaque, Skia may draw incorrectly. Does not check if SkColorType allows alpha, or if any pixel value has transparency. @return true if SkAlphaType is kOpaque_SkAlphaType */ bool isOpaque() const { return fColorInfo.isOpaque(); } /** Returns SkISize { width(), height() }. @return integral size of width() and height() */ SkISize dimensions() const { return fDimensions; } /** Returns SkIRect { 0, 0, width(), height() }. @return integral rectangle from origin to width() and height() */ SkIRect bounds() const { return SkIRect::MakeSize(fDimensions); } /** Returns true if associated SkColorSpace is not nullptr, and SkColorSpace gamma is approximately the same as sRGB. This includes the @return true if SkColorSpace gamma is approximately the same as sRGB */ bool gammaCloseToSRGB() const { return fColorInfo.gammaCloseToSRGB(); } /** Creates SkImageInfo with the same SkColorType, SkColorSpace, and SkAlphaType, with dimensions set to width and height. @param newWidth pixel column count; must be zero or greater @param newHeight pixel row count; must be zero or greater @return created SkImageInfo */ SkImageInfo makeWH(int newWidth, int newHeight) const { return Make({newWidth, newHeight}, fColorInfo); } /** Creates SkImageInfo with the same SkColorType, SkColorSpace, and SkAlphaType, with dimensions set to newDimensions. @param newSize pixel column and row count; must be zero or greater @return created SkImageInfo */ SkImageInfo makeDimensions(SkISize newSize) const { return Make(newSize, fColorInfo); } /** Creates SkImageInfo with same SkColorType, SkColorSpace, width, and height, with SkAlphaType set to newAlphaType. Created SkImageInfo contains newAlphaType even if it is incompatible with SkColorType, in which case SkAlphaType in SkImageInfo is ignored. @return created SkImageInfo */ SkImageInfo makeAlphaType(SkAlphaType newAlphaType) const { return Make(fDimensions, fColorInfo.makeAlphaType(newAlphaType)); } /** Creates SkImageInfo with same SkAlphaType, SkColorSpace, width, and height, with SkColorType set to newColorType. @return created SkImageInfo */ SkImageInfo makeColorType(SkColorType newColorType) const { return Make(fDimensions, fColorInfo.makeColorType(newColorType)); } /** Creates SkImageInfo with same SkAlphaType, SkColorType, width, and height, with SkColorSpace set to cs. @param cs range of colors; may be nullptr @return created SkImageInfo */ SkImageInfo makeColorSpace(sk_sp cs) const; /** Returns number of bytes per pixel required by SkColorType. Returns zero if colorType( is kUnknown_SkColorType. @return bytes in pixel */ int bytesPerPixel() const { return fColorInfo.bytesPerPixel(); } /** Returns bit shift converting row bytes to row pixels. Returns zero for kUnknown_SkColorType. @return one of: 0, 1, 2, 3; left shift to convert pixels to bytes */ int shiftPerPixel() const { return fColorInfo.shiftPerPixel(); } /** Returns minimum bytes per row, computed from pixel width() and SkColorType, which specifies bytesPerPixel(). SkBitmap maximum value for row bytes must fit in 31 bits. @return width() times bytesPerPixel() as unsigned 64-bit integer */ uint64_t minRowBytes64() const { return (uint64_t)sk_64_mul(this->width(), this->bytesPerPixel()); } /** Returns minimum bytes per row, computed from pixel width() and SkColorType, which specifies bytesPerPixel(). SkBitmap maximum value for row bytes must fit in 31 bits. @return width() times bytesPerPixel() as size_t */ size_t minRowBytes() const { uint64_t minRowBytes = this->minRowBytes64(); if (!SkTFitsIn(minRowBytes)) { return 0; } return (size_t)minRowBytes; } /** Returns byte offset of pixel from pixel base address. Asserts in debug build if x or y is outside of bounds. Does not assert if rowBytes is smaller than minRowBytes(), even though result may be incorrect. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @param rowBytes size of pixel row or larger @return offset within pixel array example: https://fiddle.skia.org/c/@ImageInfo_computeOffset */ size_t computeOffset(int x, int y, size_t rowBytes) const; /** Compares SkImageInfo with other, and returns true if width, height, SkColorType, SkAlphaType, and SkColorSpace are equivalent. @param other SkImageInfo to compare @return true if SkImageInfo equals other */ bool operator==(const SkImageInfo& other) const { return fDimensions == other.fDimensions && fColorInfo == other.fColorInfo; } /** Compares SkImageInfo with other, and returns true if width, height, SkColorType, SkAlphaType, and SkColorSpace are not equivalent. @param other SkImageInfo to compare @return true if SkImageInfo is not equal to other */ bool operator!=(const SkImageInfo& other) const { return !(*this == other); } /** Returns storage required by pixel array, given SkImageInfo dimensions, SkColorType, and rowBytes. rowBytes is assumed to be at least as large as minRowBytes(). Returns zero if height is zero. Returns SIZE_MAX if answer exceeds the range of size_t. @param rowBytes size of pixel row or larger @return memory required by pixel buffer */ size_t computeByteSize(size_t rowBytes) const; /** Returns storage required by pixel array, given SkImageInfo dimensions, and SkColorType. Uses minRowBytes() to compute bytes for pixel row. Returns zero if height is zero. Returns SIZE_MAX if answer exceeds the range of size_t. @return least memory required by pixel buffer */ size_t computeMinByteSize() const { return this->computeByteSize(this->minRowBytes()); } /** Returns true if byteSize equals SIZE_MAX. computeByteSize() and computeMinByteSize() return SIZE_MAX if size_t can not hold buffer size. @param byteSize result of computeByteSize() or computeMinByteSize() @return true if computeByteSize() or computeMinByteSize() result exceeds size_t */ static bool ByteSizeOverflowed(size_t byteSize) { return SIZE_MAX == byteSize; } /** Returns true if rowBytes is valid for this SkImageInfo. @param rowBytes size of pixel row including padding @return true if rowBytes is large enough to contain pixel row and is properly aligned */ bool validRowBytes(size_t rowBytes) const { if (rowBytes < this->minRowBytes64()) { return false; } int shift = this->shiftPerPixel(); size_t alignedRowBytes = rowBytes >> shift << shift; return alignedRowBytes == rowBytes; } /** Creates an empty SkImageInfo with kUnknown_SkColorType, kUnknown_SkAlphaType, a width and height of zero, and no SkColorSpace. */ void reset() { *this = {}; } /** Asserts if internal values are illegal or inconsistent. Only available if SK_DEBUG is defined at compile time. */ SkDEBUGCODE(void validate() const;) private: SkColorInfo fColorInfo; SkISize fDimensions = {0, 0}; SkImageInfo(SkISize dimensions, const SkColorInfo& colorInfo) : fColorInfo(colorInfo), fDimensions(dimensions) {} SkImageInfo(SkISize dimensions, SkColorInfo&& colorInfo) : fColorInfo(std::move(colorInfo)), fDimensions(dimensions) {} }; enum class SkFilterMode { kNearest, // single sample point (nearest neighbor) kLinear, // interporate between 2x2 sample points (bilinear interpolation) kLast = kLinear, }; static constexpr int kSkFilterModeCount = static_cast(SkFilterMode::kLast) + 1; enum class SkMipmapMode { kNone, // ignore mipmap levels, sample from the "base" kNearest, // sample from the nearest level kLinear, // interpolate between the two nearest levels kLast = kLinear, }; static constexpr int kSkMipmapModeCount = static_cast(SkMipmapMode::kLast) + 1; /* * Specify B and C (each between 0...1) to create a shader that applies the corresponding * cubic reconstruction filter to the image. * * Example values: * B = 1/3, C = 1/3 "Mitchell" filter * B = 0, C = 1/2 "Catmull-Rom" filter * * See "Reconstruction Filters in Computer Graphics" * Don P. Mitchell * Arun N. Netravali * 1988 * https://www.cs.utexas.edu/~fussell/courses/cs384g-fall2013/lectures/mitchell/Mitchell.pdf * * Desmos worksheet https://www.desmos.com/calculator/aghdpicrvr * Nice overview https://entropymine.com/imageworsener/bicubic/ */ struct SkCubicResampler { float B, C; // Historic default for kHigh_SkFilterQuality static constexpr SkCubicResampler Mitchell() { return {1/3.0f, 1/3.0f}; } static constexpr SkCubicResampler CatmullRom() { return {0.0f, 1/2.0f}; } }; struct SK_API SkSamplingOptions { const int maxAniso = 0; const bool useCubic = false; const SkCubicResampler cubic = {0, 0}; const SkFilterMode filter = SkFilterMode::kNearest; const SkMipmapMode mipmap = SkMipmapMode::kNone; constexpr SkSamplingOptions() = default; SkSamplingOptions(const SkSamplingOptions&) = default; SkSamplingOptions& operator=(const SkSamplingOptions& that) { this->~SkSamplingOptions(); // A pedantic no-op. new (this) SkSamplingOptions(that); return *this; } constexpr SkSamplingOptions(SkFilterMode fm, SkMipmapMode mm) : filter(fm) , mipmap(mm) {} // These are intentionally implicit because the single parameter clearly conveys what the // implicitly created SkSamplingOptions will be. constexpr SkSamplingOptions(SkFilterMode fm) : filter(fm) , mipmap(SkMipmapMode::kNone) {} constexpr SkSamplingOptions(const SkCubicResampler& c) : useCubic(true) , cubic(c) {} static constexpr SkSamplingOptions Aniso(int maxAniso) { return SkSamplingOptions{std::max(maxAniso, 1)}; } bool operator==(const SkSamplingOptions& other) const { return maxAniso == other.maxAniso && useCubic == other.useCubic && cubic.B == other.cubic.B && cubic.C == other.cubic.C && filter == other.filter && mipmap == other.mipmap; } bool operator!=(const SkSamplingOptions& other) const { return !(*this == other); } bool isAniso() const { return maxAniso != 0; } private: constexpr SkSamplingOptions(int maxAniso) : maxAniso(maxAniso) {} }; class SkColorSpace; enum SkAlphaType : int; struct SkMask; /** \class SkPixmap SkPixmap provides a utility to pair SkImageInfo with pixels and row bytes. SkPixmap is a low level class which provides convenience functions to access raster destinations. SkCanvas can not draw SkPixmap, nor does SkPixmap provide a direct drawing destination. Use SkBitmap to draw pixels referenced by SkPixmap; use SkSurface to draw into pixels referenced by SkPixmap. SkPixmap does not try to manage the lifetime of the pixel memory. Use SkPixelRef to manage pixel memory; SkPixelRef is safe across threads. */ class SK_API SkPixmap { public: /** Creates an empty SkPixmap without pixels, with kUnknown_SkColorType, with kUnknown_SkAlphaType, and with a width and height of zero. Use reset() to associate pixels, SkColorType, SkAlphaType, width, and height after SkPixmap has been created. @return empty SkPixmap */ SkPixmap() : fPixels(nullptr), fRowBytes(0), fInfo(SkImageInfo::MakeUnknown(0, 0)) {} /** Creates SkPixmap from info width, height, SkAlphaType, and SkColorType. addr points to pixels, or nullptr. rowBytes should be info.width() times info.bytesPerPixel(), or larger. No parameter checking is performed; it is up to the caller to ensure that addr and rowBytes agree with info. The memory lifetime of pixels is managed by the caller. When SkPixmap goes out of scope, addr is unaffected. SkPixmap may be later modified by reset() to change its size, pixel type, or storage. @param info width, height, SkAlphaType, SkColorType of SkImageInfo @param addr pointer to pixels allocated by caller; may be nullptr @param rowBytes size of one row of addr; width times pixel size, or larger @return initialized SkPixmap */ SkPixmap(const SkImageInfo& info, const void* addr, size_t rowBytes) : fPixels(addr), fRowBytes(rowBytes), fInfo(info) {} /** Sets width, height, row bytes to zero; pixel address to nullptr; SkColorType to kUnknown_SkColorType; and SkAlphaType to kUnknown_SkAlphaType. The prior pixels are unaffected; it is up to the caller to release pixels memory if desired. example: https://fiddle.skia.org/c/@Pixmap_reset */ void reset(); /** Sets width, height, SkAlphaType, and SkColorType from info. Sets pixel address from addr, which may be nullptr. Sets row bytes from rowBytes, which should be info.width() times info.bytesPerPixel(), or larger. Does not check addr. Asserts if built with SK_DEBUG defined and if rowBytes is too small to hold one row of pixels. The memory lifetime pixels are managed by the caller. When SkPixmap goes out of scope, addr is unaffected. @param info width, height, SkAlphaType, SkColorType of SkImageInfo @param addr pointer to pixels allocated by caller; may be nullptr @param rowBytes size of one row of addr; width times pixel size, or larger example: https://fiddle.skia.org/c/@Pixmap_reset_2 */ void reset(const SkImageInfo& info, const void* addr, size_t rowBytes); /** Changes SkColorSpace in SkImageInfo; preserves width, height, SkAlphaType, and SkColorType in SkImage, and leaves pixel address and row bytes unchanged. SkColorSpace reference count is incremented. @param colorSpace SkColorSpace moved to SkImageInfo example: https://fiddle.skia.org/c/@Pixmap_setColorSpace */ void setColorSpace(sk_sp colorSpace); /** Deprecated. */ [[nodiscard]] bool reset(const SkMask& mask); /** Sets subset width, height, pixel address to intersection of SkPixmap with area, if intersection is not empty; and return true. Otherwise, leave subset unchanged and return false. Failing to read the return value generates a compile time warning. @param subset storage for width, height, pixel address of intersection @param area bounds to intersect with SkPixmap @return true if intersection of SkPixmap and area is not empty */ [[nodiscard]] bool extractSubset(SkPixmap* subset, const SkIRect& area) const; /** Returns width, height, SkAlphaType, SkColorType, and SkColorSpace. @return reference to SkImageInfo */ const SkImageInfo& info() const { return fInfo; } /** Returns row bytes, the interval from one pixel row to the next. Row bytes is at least as large as: width() * info().bytesPerPixel(). Returns zero if colorType() is kUnknown_SkColorType. It is up to the SkBitmap creator to ensure that row bytes is a useful value. @return byte length of pixel row */ size_t rowBytes() const { return fRowBytes; } /** Returns pixel address, the base address corresponding to the pixel origin. It is up to the SkPixmap creator to ensure that pixel address is a useful value. @return pixel address */ const void* addr() const { return fPixels; } /** Returns pixel count in each pixel row. Should be equal or less than: rowBytes() / info().bytesPerPixel(). @return pixel width in SkImageInfo */ int width() const { return fInfo.width(); } /** Returns pixel row count. @return pixel height in SkImageInfo */ int height() const { return fInfo.height(); } /** * Return the dimensions of the pixmap (from its ImageInfo) */ SkISize dimensions() const { return fInfo.dimensions(); } SkColorType colorType() const { return fInfo.colorType(); } SkAlphaType alphaType() const { return fInfo.alphaType(); } /** Returns SkColorSpace, the range of colors, associated with SkImageInfo. The reference count of SkColorSpace is unchanged. The returned SkColorSpace is immutable. @return SkColorSpace in SkImageInfo, or nullptr */ SkColorSpace* colorSpace() const; /** Returns smart pointer to SkColorSpace, the range of colors, associated with SkImageInfo. The smart pointer tracks the number of objects sharing this SkColorSpace reference so the memory is released when the owners destruct. The returned SkColorSpace is immutable. @return SkColorSpace in SkImageInfo wrapped in a smart pointer */ sk_sp refColorSpace() const; /** Returns true if SkAlphaType is kOpaque_SkAlphaType. Does not check if SkColorType allows alpha, or if any pixel value has transparency. @return true if SkImageInfo has opaque SkAlphaType */ bool isOpaque() const { return fInfo.isOpaque(); } /** Returns SkIRect { 0, 0, width(), height() }. @return integral rectangle from origin to width() and height() */ SkIRect bounds() const { return SkIRect::MakeWH(this->width(), this->height()); } /** Returns number of pixels that fit on row. Should be greater than or equal to width(). @return maximum pixels per row */ int rowBytesAsPixels() const { return int(fRowBytes >> this->shiftPerPixel()); } /** Returns bit shift converting row bytes to row pixels. Returns zero for kUnknown_SkColorType. @return one of: 0, 1, 2, 3; left shift to convert pixels to bytes */ int shiftPerPixel() const { return fInfo.shiftPerPixel(); } /** Returns minimum memory required for pixel storage. Does not include unused memory on last row when rowBytesAsPixels() exceeds width(). Returns SIZE_MAX if result does not fit in size_t. Returns zero if height() or width() is 0. Returns height() times rowBytes() if colorType() is kUnknown_SkColorType. @return size in bytes of image buffer */ size_t computeByteSize() const { return fInfo.computeByteSize(fRowBytes); } /** Returns true if all pixels are opaque. SkColorType determines how pixels are encoded, and whether pixel describes alpha. Returns true for SkColorType without alpha in each pixel; for other SkColorType, returns true if all pixels have alpha values equivalent to 1.0 or greater. For SkColorType kRGB_565_SkColorType or kGray_8_SkColorType: always returns true. For SkColorType kAlpha_8_SkColorType, kBGRA_8888_SkColorType, kRGBA_8888_SkColorType: returns true if all pixel alpha values are 255. For SkColorType kARGB_4444_SkColorType: returns true if all pixel alpha values are 15. For kRGBA_F16_SkColorType: returns true if all pixel alpha values are 1.0 or greater. Returns false for kUnknown_SkColorType. @return true if all pixels have opaque values or SkColorType is opaque example: https://fiddle.skia.org/c/@Pixmap_computeIsOpaque */ bool computeIsOpaque() const; /** Returns pixel at (x, y) as unpremultiplied color. Returns black with alpha if SkColorType is kAlpha_8_SkColorType. Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined; and returns undefined values or may crash if SK_RELEASE is defined. Fails if SkColorType is kUnknown_SkColorType or pixel address is nullptr. SkColorSpace in SkImageInfo is ignored. Some color precision may be lost in the conversion to unpremultiplied color; original pixel data may have additional precision. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return pixel converted to unpremultiplied color example: https://fiddle.skia.org/c/@Pixmap_getColor */ SkColor getColor(int x, int y) const; /** Returns pixel at (x, y) as unpremultiplied color as an SkColor4f. Returns black with alpha if SkColorType is kAlpha_8_SkColorType. Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined; and returns undefined values or may crash if SK_RELEASE is defined. Fails if SkColorType is kUnknown_SkColorType or pixel address is nullptr. SkColorSpace in SkImageInfo is ignored. Some color precision may be lost in the conversion to unpremultiplied color; original pixel data may have additional precision, though this is less likely than for getColor(). Rounding errors may occur if the underlying type has lower precision. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return pixel converted to unpremultiplied float color */ SkColor4f getColor4f(int x, int y) const; /** Look up the pixel at (x,y) and return its alpha component, normalized to [0..1]. This is roughly equivalent to SkGetColorA(getColor()), but can be more efficent (and more precise if the pixels store more than 8 bits per component). @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return alpha converted to normalized float */ float getAlphaf(int x, int y) const; /** Returns readable pixel address at (x, y). Returns nullptr if SkPixelRef is nullptr. Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined. Returns nullptr if SkColorType is kUnknown_SkColorType. Performs a lookup of pixel size; for better performance, call one of: addr8, addr16, addr32, addr64, or addrF16(). @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return readable generic pointer to pixel */ const void* addr(int x, int y) const { return (const char*)fPixels + fInfo.computeOffset(x, y, fRowBytes); } /** Returns readable base pixel address. Result is addressable as unsigned 8-bit bytes. Will trigger an assert() if SkColorType is not kAlpha_8_SkColorType or kGray_8_SkColorType, and is built with SK_DEBUG defined. One byte corresponds to one pixel. @return readable unsigned 8-bit pointer to pixels */ const uint8_t* addr8() const { SkASSERT(1 == fInfo.bytesPerPixel()); return reinterpret_cast(fPixels); } /** Returns readable base pixel address. Result is addressable as unsigned 16-bit words. Will trigger an assert() if SkColorType is not kRGB_565_SkColorType or kARGB_4444_SkColorType, and is built with SK_DEBUG defined. One word corresponds to one pixel. @return readable unsigned 16-bit pointer to pixels */ const uint16_t* addr16() const { SkASSERT(2 == fInfo.bytesPerPixel()); return reinterpret_cast(fPixels); } /** Returns readable base pixel address. Result is addressable as unsigned 32-bit words. Will trigger an assert() if SkColorType is not kRGBA_8888_SkColorType or kBGRA_8888_SkColorType, and is built with SK_DEBUG defined. One word corresponds to one pixel. @return readable unsigned 32-bit pointer to pixels */ const uint32_t* addr32() const { SkASSERT(4 == fInfo.bytesPerPixel()); return reinterpret_cast(fPixels); } /** Returns readable base pixel address. Result is addressable as unsigned 64-bit words. Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built with SK_DEBUG defined. One word corresponds to one pixel. @return readable unsigned 64-bit pointer to pixels */ const uint64_t* addr64() const { SkASSERT(8 == fInfo.bytesPerPixel()); return reinterpret_cast(fPixels); } /** Returns readable base pixel address. Result is addressable as unsigned 16-bit words. Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built with SK_DEBUG defined. Each word represents one color component encoded as a half float. Four words correspond to one pixel. @return readable unsigned 16-bit pointer to first component of pixels */ const uint16_t* addrF16() const { SkASSERT(8 == fInfo.bytesPerPixel()); SkASSERT(kRGBA_F16_SkColorType == fInfo.colorType() || kRGBA_F16Norm_SkColorType == fInfo.colorType()); return reinterpret_cast(fPixels); } /** Returns readable pixel address at (x, y). Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined. Will trigger an assert() if SkColorType is not kAlpha_8_SkColorType or kGray_8_SkColorType, and is built with SK_DEBUG defined. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return readable unsigned 8-bit pointer to pixel at (x, y) */ const uint8_t* addr8(int x, int y) const { SkASSERT((unsigned)x < (unsigned)fInfo.width()); SkASSERT((unsigned)y < (unsigned)fInfo.height()); return (const uint8_t*)((const char*)this->addr8() + (size_t)y * fRowBytes + (x << 0)); } /** Returns readable pixel address at (x, y). Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined. Will trigger an assert() if SkColorType is not kRGB_565_SkColorType or kARGB_4444_SkColorType, and is built with SK_DEBUG defined. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return readable unsigned 16-bit pointer to pixel at (x, y) */ const uint16_t* addr16(int x, int y) const { SkASSERT((unsigned)x < (unsigned)fInfo.width()); SkASSERT((unsigned)y < (unsigned)fInfo.height()); return (const uint16_t*)((const char*)this->addr16() + (size_t)y * fRowBytes + (x << 1)); } /** Returns readable pixel address at (x, y). Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined. Will trigger an assert() if SkColorType is not kRGBA_8888_SkColorType or kBGRA_8888_SkColorType, and is built with SK_DEBUG defined. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return readable unsigned 32-bit pointer to pixel at (x, y) */ const uint32_t* addr32(int x, int y) const { SkASSERT((unsigned)x < (unsigned)fInfo.width()); SkASSERT((unsigned)y < (unsigned)fInfo.height()); return (const uint32_t*)((const char*)this->addr32() + (size_t)y * fRowBytes + (x << 2)); } /** Returns readable pixel address at (x, y). Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined. Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built with SK_DEBUG defined. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return readable unsigned 64-bit pointer to pixel at (x, y) */ const uint64_t* addr64(int x, int y) const { SkASSERT((unsigned)x < (unsigned)fInfo.width()); SkASSERT((unsigned)y < (unsigned)fInfo.height()); return (const uint64_t*)((const char*)this->addr64() + (size_t)y * fRowBytes + (x << 3)); } /** Returns readable pixel address at (x, y). Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined. Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built with SK_DEBUG defined. Each unsigned 16-bit word represents one color component encoded as a half float. Four words correspond to one pixel. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return readable unsigned 16-bit pointer to pixel component at (x, y) */ const uint16_t* addrF16(int x, int y) const { SkASSERT(kRGBA_F16_SkColorType == fInfo.colorType() || kRGBA_F16Norm_SkColorType == fInfo.colorType()); return reinterpret_cast(this->addr64(x, y)); } /** Returns writable base pixel address. @return writable generic base pointer to pixels */ void* writable_addr() const { return const_cast(fPixels); } /** Returns writable pixel address at (x, y). Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined. Returns zero if SkColorType is kUnknown_SkColorType. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return writable generic pointer to pixel */ void* writable_addr(int x, int y) const { return const_cast(this->addr(x, y)); } /** Returns writable pixel address at (x, y). Result is addressable as unsigned 8-bit bytes. Will trigger an assert() if SkColorType is not kAlpha_8_SkColorType or kGray_8_SkColorType, and is built with SK_DEBUG defined. One byte corresponds to one pixel. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return writable unsigned 8-bit pointer to pixels */ uint8_t* writable_addr8(int x, int y) const { return const_cast(this->addr8(x, y)); } /** Returns writable_addr pixel address at (x, y). Result is addressable as unsigned 16-bit words. Will trigger an assert() if SkColorType is not kRGB_565_SkColorType or kARGB_4444_SkColorType, and is built with SK_DEBUG defined. One word corresponds to one pixel. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return writable unsigned 16-bit pointer to pixel */ uint16_t* writable_addr16(int x, int y) const { return const_cast(this->addr16(x, y)); } /** Returns writable pixel address at (x, y). Result is addressable as unsigned 32-bit words. Will trigger an assert() if SkColorType is not kRGBA_8888_SkColorType or kBGRA_8888_SkColorType, and is built with SK_DEBUG defined. One word corresponds to one pixel. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return writable unsigned 32-bit pointer to pixel */ uint32_t* writable_addr32(int x, int y) const { return const_cast(this->addr32(x, y)); } /** Returns writable pixel address at (x, y). Result is addressable as unsigned 64-bit words. Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built with SK_DEBUG defined. One word corresponds to one pixel. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return writable unsigned 64-bit pointer to pixel */ uint64_t* writable_addr64(int x, int y) const { return const_cast(this->addr64(x, y)); } /** Returns writable pixel address at (x, y). Result is addressable as unsigned 16-bit words. Will trigger an assert() if SkColorType is not kRGBA_F16_SkColorType and is built with SK_DEBUG defined. Each word represents one color component encoded as a half float. Four words correspond to one pixel. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return writable unsigned 16-bit pointer to first component of pixel */ uint16_t* writable_addrF16(int x, int y) const { return reinterpret_cast(writable_addr64(x, y)); } /** Copies a SkRect of pixels to dstPixels. Copy starts at (0, 0), and does not exceed SkPixmap (width(), height()). dstInfo specifies width, height, SkColorType, SkAlphaType, and SkColorSpace of destination. dstRowBytes specifics the gap from one destination row to the next. Returns true if pixels are copied. Returns false if dstInfo address equals nullptr, or dstRowBytes is less than dstInfo.minRowBytes(). Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match. If SkPixmap colorType() is kGray_8_SkColorType, dstInfo.colorSpace() must match. If SkPixmap alphaType() is kOpaque_SkAlphaType, dstInfo.alphaType() must match. If SkPixmap colorSpace() is nullptr, dstInfo.colorSpace() must match. Returns false if pixel conversion is not possible. Returns false if SkPixmap width() or height() is zero or negative. @param dstInfo destination width, height, SkColorType, SkAlphaType, SkColorSpace @param dstPixels destination pixel storage @param dstRowBytes destination row length @return true if pixels are copied to dstPixels */ bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes) const { return this->readPixels(dstInfo, dstPixels, dstRowBytes, 0, 0); } /** Copies a SkRect of pixels to dstPixels. Copy starts at (srcX, srcY), and does not exceed SkPixmap (width(), height()). dstInfo specifies width, height, SkColorType, SkAlphaType, and SkColorSpace of destination. dstRowBytes specifics the gap from one destination row to the next. Returns true if pixels are copied. Returns false if dstInfo address equals nullptr, or dstRowBytes is less than dstInfo.minRowBytes(). Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match. If SkPixmap colorType() is kGray_8_SkColorType, dstInfo.colorSpace() must match. If SkPixmap alphaType() is kOpaque_SkAlphaType, dstInfo.alphaType() must match. If SkPixmap colorSpace() is nullptr, dstInfo.colorSpace() must match. Returns false if pixel conversion is not possible. srcX and srcY may be negative to copy only top or left of source. Returns false if SkPixmap width() or height() is zero or negative. Returns false if: abs(srcX) >= Pixmap width(), or if abs(srcY) >= Pixmap height(). @param dstInfo destination width, height, SkColorType, SkAlphaType, SkColorSpace @param dstPixels destination pixel storage @param dstRowBytes destination row length @param srcX column index whose absolute value is less than width() @param srcY row index whose absolute value is less than height() @return true if pixels are copied to dstPixels */ bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY) const; /** Copies a SkRect of pixels to dst. Copy starts at (srcX, srcY), and does not exceed SkPixmap (width(), height()). dst specifies width, height, SkColorType, SkAlphaType, and SkColorSpace of destination. Returns true if pixels are copied. Returns false if dst address equals nullptr, or dst.rowBytes() is less than dst SkImageInfo::minRowBytes. Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dst.info().colorType must match. If SkPixmap colorType() is kGray_8_SkColorType, dst.info().colorSpace must match. If SkPixmap alphaType() is kOpaque_SkAlphaType, dst.info().alphaType must match. If SkPixmap colorSpace() is nullptr, dst.info().colorSpace must match. Returns false if pixel conversion is not possible. srcX and srcY may be negative to copy only top or left of source. Returns false SkPixmap width() or height() is zero or negative. Returns false if: abs(srcX) >= Pixmap width(), or if abs(srcY) >= Pixmap height(). @param dst SkImageInfo and pixel address to write to @param srcX column index whose absolute value is less than width() @param srcY row index whose absolute value is less than height() @return true if pixels are copied to dst */ bool readPixels(const SkPixmap& dst, int srcX, int srcY) const { return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), srcX, srcY); } /** Copies pixels inside bounds() to dst. dst specifies width, height, SkColorType, SkAlphaType, and SkColorSpace of destination. Returns true if pixels are copied. Returns false if dst address equals nullptr, or dst.rowBytes() is less than dst SkImageInfo::minRowBytes. Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. If SkPixmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. If SkPixmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must match. If SkPixmap colorSpace() is nullptr, dst SkColorSpace must match. Returns false if pixel conversion is not possible. Returns false if SkPixmap width() or height() is zero or negative. @param dst SkImageInfo and pixel address to write to @return true if pixels are copied to dst */ bool readPixels(const SkPixmap& dst) const { return this->readPixels(dst.info(), dst.writable_addr(), dst.rowBytes(), 0, 0); } /** Copies SkBitmap to dst, scaling pixels to fit dst.width() and dst.height(), and converting pixels to match dst.colorType() and dst.alphaType(). Returns true if pixels are copied. Returns false if dst address is nullptr, or dst.rowBytes() is less than dst SkImageInfo::minRowBytes. Pixels are copied only if pixel conversion is possible. If SkPixmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. If SkPixmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. If SkPixmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must match. If SkPixmap colorSpace() is nullptr, dst SkColorSpace must match. Returns false if pixel conversion is not possible. Returns false if SkBitmap width() or height() is zero or negative. @param dst SkImageInfo and pixel address to write to @return true if pixels are scaled to fit dst example: https://fiddle.skia.org/c/@Pixmap_scalePixels */ bool scalePixels(const SkPixmap& dst, const SkSamplingOptions&) const; /** Writes color to pixels bounded by subset; returns true on success. Returns false if colorType() is kUnknown_SkColorType, or if subset does not intersect bounds(). @param color sRGB unpremultiplied color to write @param subset bounding integer SkRect of written pixels @return true if pixels are changed example: https://fiddle.skia.org/c/@Pixmap_erase */ bool erase(SkColor color, const SkIRect& subset) const; /** Writes color to pixels inside bounds(); returns true on success. Returns false if colorType() is kUnknown_SkColorType, or if bounds() is empty. @param color sRGB unpremultiplied color to write @return true if pixels are changed */ bool erase(SkColor color) const { return this->erase(color, this->bounds()); } /** Writes color to pixels bounded by subset; returns true on success. if subset is nullptr, writes colors pixels inside bounds(). Returns false if colorType() is kUnknown_SkColorType, if subset is not nullptr and does not intersect bounds(), or if subset is nullptr and bounds() is empty. @param color unpremultiplied color to write @param subset bounding integer SkRect of pixels to write; may be nullptr @return true if pixels are changed */ bool erase(const SkColor4f& color, const SkIRect* subset = nullptr) const; private: const void* fPixels; size_t fRowBytes; SkImageInfo fInfo; }; class SkColorSpace; class SkImage; class SkMatrix; class SkMipmap; class SkPaint; class SkPixelRef; class SkShader; enum SkColorType : int; enum class SkTileMode; struct SkMaskBuilder; /** \class SkBitmap SkBitmap describes a two-dimensional raster pixel array. SkBitmap is built on SkImageInfo, containing integer width and height, SkColorType and SkAlphaType describing the pixel format, and SkColorSpace describing the range of colors. SkBitmap points to SkPixelRef, which describes the physical array of pixels. SkImageInfo bounds may be located anywhere fully inside SkPixelRef bounds. SkBitmap can be drawn using SkCanvas. SkBitmap can be a drawing destination for SkCanvas draw member functions. SkBitmap flexibility as a pixel container limits some optimizations available to the target platform. If pixel array is primarily read-only, use SkImage for better performance. If pixel array is primarily written to, use SkSurface for better performance. Declaring SkBitmap const prevents altering SkImageInfo: the SkBitmap height, width, and so on cannot change. It does not affect SkPixelRef: a caller may write its pixels. Declaring SkBitmap const affects SkBitmap configuration, not its contents. SkBitmap is not thread safe. Each thread must have its own copy of SkBitmap fields, although threads may share the underlying pixel array. */ class SK_API SkBitmap { public: class SK_API Allocator; /** Creates an empty SkBitmap without pixels, with kUnknown_SkColorType, kUnknown_SkAlphaType, and with a width and height of zero. SkPixelRef origin is set to (0, 0). Use setInfo() to associate SkColorType, SkAlphaType, width, and height after SkBitmap has been created. @return empty SkBitmap example: https://fiddle.skia.org/c/@Bitmap_empty_constructor */ SkBitmap(); /** Copies settings from src to returned SkBitmap. Shares pixels if src has pixels allocated, so both bitmaps reference the same pixels. @param src SkBitmap to copy SkImageInfo, and share SkPixelRef @return copy of src example: https://fiddle.skia.org/c/@Bitmap_copy_const_SkBitmap */ SkBitmap(const SkBitmap& src); /** Copies settings from src to returned SkBitmap. Moves ownership of src pixels to SkBitmap. @param src SkBitmap to copy SkImageInfo, and reassign SkPixelRef @return copy of src example: https://fiddle.skia.org/c/@Bitmap_move_SkBitmap */ SkBitmap(SkBitmap&& src); /** Decrements SkPixelRef reference count, if SkPixelRef is not nullptr. */ ~SkBitmap(); /** Copies settings from src to returned SkBitmap. Shares pixels if src has pixels allocated, so both bitmaps reference the same pixels. @param src SkBitmap to copy SkImageInfo, and share SkPixelRef @return copy of src example: https://fiddle.skia.org/c/@Bitmap_copy_operator */ SkBitmap& operator=(const SkBitmap& src); /** Copies settings from src to returned SkBitmap. Moves ownership of src pixels to SkBitmap. @param src SkBitmap to copy SkImageInfo, and reassign SkPixelRef @return copy of src example: https://fiddle.skia.org/c/@Bitmap_move_operator */ SkBitmap& operator=(SkBitmap&& src); /** Swaps the fields of the two bitmaps. @param other SkBitmap exchanged with original example: https://fiddle.skia.org/c/@Bitmap_swap */ void swap(SkBitmap& other); /** Returns a constant reference to the SkPixmap holding the SkBitmap pixel address, row bytes, and SkImageInfo. @return reference to SkPixmap describing this SkBitmap */ const SkPixmap& pixmap() const { return fPixmap; } /** Returns width, height, SkAlphaType, SkColorType, and SkColorSpace. @return reference to SkImageInfo */ const SkImageInfo& info() const { return fPixmap.info(); } /** Returns pixel count in each row. Should be equal or less than rowBytes() / info().bytesPerPixel(). May be less than pixelRef().width(). Will not exceed pixelRef().width() less pixelRefOrigin().fX. @return pixel width in SkImageInfo */ int width() const { return fPixmap.width(); } /** Returns pixel row count. Maybe be less than pixelRef().height(). Will not exceed pixelRef().height() less pixelRefOrigin().fY. @return pixel height in SkImageInfo */ int height() const { return fPixmap.height(); } SkColorType colorType() const { return fPixmap.colorType(); } SkAlphaType alphaType() const { return fPixmap.alphaType(); } /** Returns SkColorSpace, the range of colors, associated with SkImageInfo. The reference count of SkColorSpace is unchanged. The returned SkColorSpace is immutable. @return SkColorSpace in SkImageInfo, or nullptr */ SkColorSpace* colorSpace() const; /** Returns smart pointer to SkColorSpace, the range of colors, associated with SkImageInfo. The smart pointer tracks the number of objects sharing this SkColorSpace reference so the memory is released when the owners destruct. The returned SkColorSpace is immutable. @return SkColorSpace in SkImageInfo wrapped in a smart pointer */ sk_sp refColorSpace() const; /** Returns number of bytes per pixel required by SkColorType. Returns zero if colorType( is kUnknown_SkColorType. @return bytes in pixel */ int bytesPerPixel() const { return fPixmap.info().bytesPerPixel(); } /** Returns number of pixels that fit on row. Should be greater than or equal to width(). @return maximum pixels per row */ int rowBytesAsPixels() const { return fPixmap.rowBytesAsPixels(); } /** Returns bit shift converting row bytes to row pixels. Returns zero for kUnknown_SkColorType. @return one of: 0, 1, 2, 3; left shift to convert pixels to bytes */ int shiftPerPixel() const { return fPixmap.shiftPerPixel(); } /** Returns true if either width() or height() are zero. Does not check if SkPixelRef is nullptr; call drawsNothing() to check width(), height(), and SkPixelRef. @return true if dimensions do not enclose area */ bool empty() const { return fPixmap.info().isEmpty(); } /** Returns true if SkPixelRef is nullptr. Does not check if width() or height() are zero; call drawsNothing() to check width(), height(), and SkPixelRef. @return true if no SkPixelRef is associated */ bool isNull() const { return nullptr == fPixelRef; } /** Returns true if width() or height() are zero, or if SkPixelRef is nullptr. If true, SkBitmap has no effect when drawn or drawn into. @return true if drawing has no effect */ bool drawsNothing() const { return this->empty() || this->isNull(); } /** Returns row bytes, the interval from one pixel row to the next. Row bytes is at least as large as: width() * info().bytesPerPixel(). Returns zero if colorType() is kUnknown_SkColorType, or if row bytes supplied to setInfo() is not large enough to hold a row of pixels. @return byte length of pixel row */ size_t rowBytes() const { return fPixmap.rowBytes(); } /** Sets SkAlphaType, if alphaType is compatible with SkColorType. Returns true unless alphaType is kUnknown_SkAlphaType and current SkAlphaType is not kUnknown_SkAlphaType. Returns true if SkColorType is kUnknown_SkColorType. alphaType is ignored, and SkAlphaType remains kUnknown_SkAlphaType. Returns true if SkColorType is kRGB_565_SkColorType or kGray_8_SkColorType. alphaType is ignored, and SkAlphaType remains kOpaque_SkAlphaType. If SkColorType is kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kBGRA_8888_SkColorType, or kRGBA_F16_SkColorType: returns true unless alphaType is kUnknown_SkAlphaType and SkAlphaType is not kUnknown_SkAlphaType. If SkAlphaType is kUnknown_SkAlphaType, alphaType is ignored. If SkColorType is kAlpha_8_SkColorType, returns true unless alphaType is kUnknown_SkAlphaType and SkAlphaType is not kUnknown_SkAlphaType. If SkAlphaType is kUnknown_SkAlphaType, alphaType is ignored. If alphaType is kUnpremul_SkAlphaType, it is treated as kPremul_SkAlphaType. This changes SkAlphaType in SkPixelRef; all bitmaps sharing SkPixelRef are affected. @return true if SkAlphaType is set example: https://fiddle.skia.org/c/@Bitmap_setAlphaType */ bool setAlphaType(SkAlphaType alphaType); /** Returns pixel address, the base address corresponding to the pixel origin. @return pixel address */ void* getPixels() const { return fPixmap.writable_addr(); } /** Returns minimum memory required for pixel storage. Does not include unused memory on last row when rowBytesAsPixels() exceeds width(). Returns SIZE_MAX if result does not fit in size_t. Returns zero if height() or width() is 0. Returns height() times rowBytes() if colorType() is kUnknown_SkColorType. @return size in bytes of image buffer */ size_t computeByteSize() const { return fPixmap.computeByteSize(); } /** Returns true if pixels can not change. Most immutable SkBitmap checks trigger an assert only on debug builds. @return true if pixels are immutable example: https://fiddle.skia.org/c/@Bitmap_isImmutable */ bool isImmutable() const; /** Sets internal flag to mark SkBitmap as immutable. Once set, pixels can not change. Any other bitmap sharing the same SkPixelRef are also marked as immutable. Once SkPixelRef is marked immutable, the setting cannot be cleared. Writing to immutable SkBitmap pixels triggers an assert on debug builds. example: https://fiddle.skia.org/c/@Bitmap_setImmutable */ void setImmutable(); /** Returns true if SkAlphaType is set to hint that all pixels are opaque; their alpha value is implicitly or explicitly 1.0. If true, and all pixels are not opaque, Skia may draw incorrectly. Does not check if SkColorType allows alpha, or if any pixel value has transparency. @return true if SkImageInfo SkAlphaType is kOpaque_SkAlphaType */ bool isOpaque() const { return SkAlphaTypeIsOpaque(this->alphaType()); } /** Resets to its initial state; all fields are set to zero, as if SkBitmap had been initialized by SkBitmap(). Sets width, height, row bytes to zero; pixel address to nullptr; SkColorType to kUnknown_SkColorType; and SkAlphaType to kUnknown_SkAlphaType. If SkPixelRef is allocated, its reference count is decreased by one, releasing its memory if SkBitmap is the sole owner. example: https://fiddle.skia.org/c/@Bitmap_reset */ void reset(); /** Returns true if all pixels are opaque. SkColorType determines how pixels are encoded, and whether pixel describes alpha. Returns true for SkColorType without alpha in each pixel; for other SkColorType, returns true if all pixels have alpha values equivalent to 1.0 or greater. For SkColorType kRGB_565_SkColorType or kGray_8_SkColorType: always returns true. For SkColorType kAlpha_8_SkColorType, kBGRA_8888_SkColorType, kRGBA_8888_SkColorType: returns true if all pixel alpha values are 255. For SkColorType kARGB_4444_SkColorType: returns true if all pixel alpha values are 15. For kRGBA_F16_SkColorType: returns true if all pixel alpha values are 1.0 or greater. Returns false for kUnknown_SkColorType. @param bm SkBitmap to check @return true if all pixels have opaque values or SkColorType is opaque */ static bool ComputeIsOpaque(const SkBitmap& bm) { return bm.pixmap().computeIsOpaque(); } /** Returns SkRect { 0, 0, width(), height() }. @param bounds container for floating point rectangle example: https://fiddle.skia.org/c/@Bitmap_getBounds */ void getBounds(SkRect* bounds) const; /** Returns SkIRect { 0, 0, width(), height() }. @param bounds container for integral rectangle example: https://fiddle.skia.org/c/@Bitmap_getBounds_2 */ void getBounds(SkIRect* bounds) const; /** Returns SkIRect { 0, 0, width(), height() }. @return integral rectangle from origin to width() and height() */ SkIRect bounds() const { return fPixmap.info().bounds(); } /** Returns SkISize { width(), height() }. @return integral size of width() and height() */ SkISize dimensions() const { return fPixmap.info().dimensions(); } /** Returns the bounds of this bitmap, offset by its SkPixelRef origin. @return bounds within SkPixelRef bounds */ SkIRect getSubset() const { SkIPoint origin = this->pixelRefOrigin(); return SkIRect::MakeXYWH(origin.x(), origin.y(), this->width(), this->height()); } /** Sets width, height, SkAlphaType, SkColorType, SkColorSpace, and optional rowBytes. Frees pixels, and returns true if successful. imageInfo.alphaType() may be altered to a value permitted by imageInfo.colorSpace(). If imageInfo.colorType() is kUnknown_SkColorType, imageInfo.alphaType() is set to kUnknown_SkAlphaType. If imageInfo.colorType() is kAlpha_8_SkColorType and imageInfo.alphaType() is kUnpremul_SkAlphaType, imageInfo.alphaType() is replaced by kPremul_SkAlphaType. If imageInfo.colorType() is kRGB_565_SkColorType or kGray_8_SkColorType, imageInfo.alphaType() is set to kOpaque_SkAlphaType. If imageInfo.colorType() is kARGB_4444_SkColorType, kRGBA_8888_SkColorType, kBGRA_8888_SkColorType, or kRGBA_F16_SkColorType: imageInfo.alphaType() remains unchanged. rowBytes must equal or exceed imageInfo.minRowBytes(). If imageInfo.colorSpace() is kUnknown_SkColorType, rowBytes is ignored and treated as zero; for all other SkColorSpace values, rowBytes of zero is treated as imageInfo.minRowBytes(). Calls reset() and returns false if: - rowBytes exceeds 31 bits - imageInfo.width() is negative - imageInfo.height() is negative - rowBytes is positive and less than imageInfo.width() times imageInfo.bytesPerPixel() @param imageInfo contains width, height, SkAlphaType, SkColorType, SkColorSpace @param rowBytes imageInfo.minRowBytes() or larger; or zero @return true if SkImageInfo set successfully example: https://fiddle.skia.org/c/@Bitmap_setInfo */ bool setInfo(const SkImageInfo& imageInfo, size_t rowBytes = 0); /** \enum SkBitmap::AllocFlags AllocFlags is obsolete. We always zero pixel memory when allocated. */ enum AllocFlags { kZeroPixels_AllocFlag = 1 << 0, //!< zero pixel memory. No effect. This is the default. }; /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel memory. Memory is zeroed. Returns false and calls reset() if SkImageInfo could not be set, or memory could not be allocated, or memory could not optionally be zeroed. On most platforms, allocating pixel memory may succeed even though there is not sufficient memory to hold pixels; allocation does not take place until the pixels are written to. The actual behavior depends on the platform implementation of calloc(). @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace @param flags kZeroPixels_AllocFlag, or zero @return true if pixels allocation is successful */ [[nodiscard]] bool tryAllocPixelsFlags(const SkImageInfo& info, uint32_t flags); /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel memory. Memory is zeroed. Aborts execution if SkImageInfo could not be set, or memory could not be allocated, or memory could not optionally be zeroed. Abort steps may be provided by the user at compile time by defining SK_ABORT. On most platforms, allocating pixel memory may succeed even though there is not sufficient memory to hold pixels; allocation does not take place until the pixels are written to. The actual behavior depends on the platform implementation of calloc(). @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace @param flags kZeroPixels_AllocFlag, or zero example: https://fiddle.skia.org/c/@Bitmap_allocPixelsFlags */ void allocPixelsFlags(const SkImageInfo& info, uint32_t flags); /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel memory. rowBytes must equal or exceed info.width() times info.bytesPerPixel(), or equal zero. Pass in zero for rowBytes to compute the minimum valid value. Returns false and calls reset() if SkImageInfo could not be set, or memory could not be allocated. On most platforms, allocating pixel memory may succeed even though there is not sufficient memory to hold pixels; allocation does not take place until the pixels are written to. The actual behavior depends on the platform implementation of malloc(). @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace @param rowBytes size of pixel row or larger; may be zero @return true if pixel storage is allocated */ [[nodiscard]] bool tryAllocPixels(const SkImageInfo& info, size_t rowBytes); /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel memory. rowBytes must equal or exceed info.width() times info.bytesPerPixel(), or equal zero. Pass in zero for rowBytes to compute the minimum valid value. Aborts execution if SkImageInfo could not be set, or memory could not be allocated. Abort steps may be provided by the user at compile time by defining SK_ABORT. On most platforms, allocating pixel memory may succeed even though there is not sufficient memory to hold pixels; allocation does not take place until the pixels are written to. The actual behavior depends on the platform implementation of malloc(). @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace @param rowBytes size of pixel row or larger; may be zero example: https://fiddle.skia.org/c/@Bitmap_allocPixels */ void allocPixels(const SkImageInfo& info, size_t rowBytes); /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel memory. Returns false and calls reset() if SkImageInfo could not be set, or memory could not be allocated. On most platforms, allocating pixel memory may succeed even though there is not sufficient memory to hold pixels; allocation does not take place until the pixels are written to. The actual behavior depends on the platform implementation of malloc(). @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace @return true if pixel storage is allocated */ [[nodiscard]] bool tryAllocPixels(const SkImageInfo& info) { return this->tryAllocPixels(info, info.minRowBytes()); } /** Sets SkImageInfo to info following the rules in setInfo() and allocates pixel memory. Aborts execution if SkImageInfo could not be set, or memory could not be allocated. Abort steps may be provided by the user at compile time by defining SK_ABORT. On most platforms, allocating pixel memory may succeed even though there is not sufficient memory to hold pixels; allocation does not take place until the pixels are written to. The actual behavior depends on the platform implementation of malloc(). @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace example: https://fiddle.skia.org/c/@Bitmap_allocPixels_2 */ void allocPixels(const SkImageInfo& info); /** Sets SkImageInfo to width, height, and native color type; and allocates pixel memory. If isOpaque is true, sets SkImageInfo to kOpaque_SkAlphaType; otherwise, sets to kPremul_SkAlphaType. Calls reset() and returns false if width exceeds 29 bits or is negative, or height is negative. Returns false if allocation fails. Use to create SkBitmap that matches SkPMColor, the native pixel arrangement on the platform. SkBitmap drawn to output device skips converting its pixel format. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @param isOpaque true if pixels do not have transparency @return true if pixel storage is allocated */ [[nodiscard]] bool tryAllocN32Pixels(int width, int height, bool isOpaque = false); /** Sets SkImageInfo to width, height, and the native color type; and allocates pixel memory. If isOpaque is true, sets SkImageInfo to kOpaque_SkAlphaType; otherwise, sets to kPremul_SkAlphaType. Aborts if width exceeds 29 bits or is negative, or height is negative, or allocation fails. Abort steps may be provided by the user at compile time by defining SK_ABORT. Use to create SkBitmap that matches SkPMColor, the native pixel arrangement on the platform. SkBitmap drawn to output device skips converting its pixel format. @param width pixel column count; must be zero or greater @param height pixel row count; must be zero or greater @param isOpaque true if pixels do not have transparency example: https://fiddle.skia.org/c/@Bitmap_allocN32Pixels */ void allocN32Pixels(int width, int height, bool isOpaque = false); /** Sets SkImageInfo to info following the rules in setInfo(), and creates SkPixelRef containing pixels and rowBytes. releaseProc, if not nullptr, is called immediately on failure or when pixels are no longer referenced. context may be nullptr. If SkImageInfo could not be set, or rowBytes is less than info.minRowBytes(): calls releaseProc if present, calls reset(), and returns false. Otherwise, if pixels equals nullptr: sets SkImageInfo, calls releaseProc if present, returns true. If SkImageInfo is set, pixels is not nullptr, and releaseProc is not nullptr: when pixels are no longer referenced, calls releaseProc with pixels and context as parameters. @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace @param pixels address or pixel storage; may be nullptr @param rowBytes size of pixel row or larger @param releaseProc function called when pixels can be deleted; may be nullptr @param context caller state passed to releaseProc; may be nullptr @return true if SkImageInfo is set to info */ bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, void (*releaseProc)(void* addr, void* context), void* context); /** Sets SkImageInfo to info following the rules in setInfo(), and creates SkPixelRef containing pixels and rowBytes. If SkImageInfo could not be set, or rowBytes is less than info.minRowBytes(): calls reset(), and returns false. Otherwise, if pixels equals nullptr: sets SkImageInfo, returns true. Caller must ensure that pixels are valid for the lifetime of SkBitmap and SkPixelRef. @param info contains width, height, SkAlphaType, SkColorType, SkColorSpace @param pixels address or pixel storage; may be nullptr @param rowBytes size of pixel row or larger @return true if SkImageInfo is set to info */ bool installPixels(const SkImageInfo& info, void* pixels, size_t rowBytes) { return this->installPixels(info, pixels, rowBytes, nullptr, nullptr); } /** Sets SkImageInfo to pixmap.info() following the rules in setInfo(), and creates SkPixelRef containing pixmap.addr() and pixmap.rowBytes(). If SkImageInfo could not be set, or pixmap.rowBytes() is less than SkImageInfo::minRowBytes(): calls reset(), and returns false. Otherwise, if pixmap.addr() equals nullptr: sets SkImageInfo, returns true. Caller must ensure that pixmap is valid for the lifetime of SkBitmap and SkPixelRef. @param pixmap SkImageInfo, pixel address, and rowBytes() @return true if SkImageInfo was set to pixmap.info() example: https://fiddle.skia.org/c/@Bitmap_installPixels_3 */ bool installPixels(const SkPixmap& pixmap); /** Deprecated. */ bool installMaskPixels(SkMaskBuilder& mask); /** Replaces SkPixelRef with pixels, preserving SkImageInfo and rowBytes(). Sets SkPixelRef origin to (0, 0). If pixels is nullptr, or if info().colorType() equals kUnknown_SkColorType; release reference to SkPixelRef, and set SkPixelRef to nullptr. Caller is responsible for handling ownership pixel memory for the lifetime of SkBitmap and SkPixelRef. @param pixels address of pixel storage, managed by caller example: https://fiddle.skia.org/c/@Bitmap_setPixels */ void setPixels(void* pixels); /** Allocates pixel memory with HeapAllocator, and replaces existing SkPixelRef. The allocation size is determined by SkImageInfo width, height, and SkColorType. Returns false if info().colorType() is kUnknown_SkColorType, or allocation fails. @return true if the allocation succeeds */ [[nodiscard]] bool tryAllocPixels() { return this->tryAllocPixels((Allocator*)nullptr); } /** Allocates pixel memory with HeapAllocator, and replaces existing SkPixelRef. The allocation size is determined by SkImageInfo width, height, and SkColorType. Aborts if info().colorType() is kUnknown_SkColorType, or allocation fails. Abort steps may be provided by the user at compile time by defining SK_ABORT. example: https://fiddle.skia.org/c/@Bitmap_allocPixels_3 */ void allocPixels(); /** Allocates pixel memory with allocator, and replaces existing SkPixelRef. The allocation size is determined by SkImageInfo width, height, and SkColorType. If allocator is nullptr, use HeapAllocator instead. Returns false if Allocator::allocPixelRef return false. @param allocator instance of SkBitmap::Allocator instantiation @return true if custom allocator reports success */ [[nodiscard]] bool tryAllocPixels(Allocator* allocator); /** Allocates pixel memory with allocator, and replaces existing SkPixelRef. The allocation size is determined by SkImageInfo width, height, and SkColorType. If allocator is nullptr, use HeapAllocator instead. Aborts if Allocator::allocPixelRef return false. Abort steps may be provided by the user at compile time by defining SK_ABORT. @param allocator instance of SkBitmap::Allocator instantiation example: https://fiddle.skia.org/c/@Bitmap_allocPixels_4 */ void allocPixels(Allocator* allocator); /** Returns SkPixelRef, which contains: pixel base address; its dimensions; and rowBytes(), the interval from one row to the next. Does not change SkPixelRef reference count. SkPixelRef may be shared by multiple bitmaps. If SkPixelRef has not been set, returns nullptr. @return SkPixelRef, or nullptr */ SkPixelRef* pixelRef() const { return fPixelRef.get(); } /** Returns origin of pixels within SkPixelRef. SkBitmap bounds is always contained by SkPixelRef bounds, which may be the same size or larger. Multiple SkBitmap can share the same SkPixelRef, where each SkBitmap has different bounds. The returned origin added to SkBitmap dimensions equals or is smaller than the SkPixelRef dimensions. Returns (0, 0) if SkPixelRef is nullptr. @return pixel origin within SkPixelRef example: https://fiddle.skia.org/c/@Bitmap_pixelRefOrigin */ SkIPoint pixelRefOrigin() const; /** Replaces pixelRef and origin in SkBitmap. dx and dy specify the offset within the SkPixelRef pixels for the top-left corner of the bitmap. Asserts in debug builds if dx or dy are out of range. Pins dx and dy to legal range in release builds. The caller is responsible for ensuring that the pixels match the SkColorType and SkAlphaType in SkImageInfo. @param pixelRef SkPixelRef describing pixel address and rowBytes() @param dx column offset in SkPixelRef for bitmap origin @param dy row offset in SkPixelRef for bitmap origin example: https://fiddle.skia.org/c/@Bitmap_setPixelRef */ void setPixelRef(sk_sp pixelRef, int dx, int dy); /** Returns true if SkBitmap is can be drawn. @return true if getPixels() is not nullptr */ bool readyToDraw() const { return this->getPixels() != nullptr; } /** Returns a unique value corresponding to the pixels in SkPixelRef. Returns a different value after notifyPixelsChanged() has been called. Returns zero if SkPixelRef is nullptr. Determines if pixels have changed since last examined. @return unique value for pixels in SkPixelRef example: https://fiddle.skia.org/c/@Bitmap_getGenerationID */ uint32_t getGenerationID() const; /** Marks that pixels in SkPixelRef have changed. Subsequent calls to getGenerationID() return a different value. example: https://fiddle.skia.org/c/@Bitmap_notifyPixelsChanged */ void notifyPixelsChanged() const; /** Replaces pixel values with c, interpreted as being in the sRGB SkColorSpace. All pixels contained by bounds() are affected. If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then alpha is ignored; RGB is treated as opaque. If colorType() is kAlpha_8_SkColorType, then RGB is ignored. @param c unpremultiplied color example: https://fiddle.skia.org/c/@Bitmap_eraseColor */ void eraseColor(SkColor4f) const; /** Replaces pixel values with c, interpreted as being in the sRGB SkColorSpace. All pixels contained by bounds() are affected. If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then alpha is ignored; RGB is treated as opaque. If colorType() is kAlpha_8_SkColorType, then RGB is ignored. Input color is ultimately converted to an SkColor4f, so eraseColor(SkColor4f c) will have higher color resolution. @param c unpremultiplied color. example: https://fiddle.skia.org/c/@Bitmap_eraseColor */ void eraseColor(SkColor c) const; /** Replaces pixel values with unpremultiplied color built from a, r, g, and b, interpreted as being in the sRGB SkColorSpace. All pixels contained by bounds() are affected. If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then a is ignored; r, g, and b are treated as opaque. If colorType() is kAlpha_8_SkColorType, then r, g, and b are ignored. @param a amount of alpha, from fully transparent (0) to fully opaque (255) @param r amount of red, from no red (0) to full red (255) @param g amount of green, from no green (0) to full green (255) @param b amount of blue, from no blue (0) to full blue (255) */ void eraseARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b) const { this->eraseColor(SkColorSetARGB(a, r, g, b)); } /** Replaces pixel values inside area with c. interpreted as being in the sRGB SkColorSpace. If area does not intersect bounds(), call has no effect. If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then alpha is ignored; RGB is treated as opaque. If colorType() is kAlpha_8_SkColorType, then RGB is ignored. @param c unpremultiplied color @param area rectangle to fill example: https://fiddle.skia.org/c/@Bitmap_erase */ void erase(SkColor4f c, const SkIRect& area) const; /** Replaces pixel values inside area with c. interpreted as being in the sRGB SkColorSpace. If area does not intersect bounds(), call has no effect. If the colorType() is kGray_8_SkColorType or kRGB_565_SkColorType, then alpha is ignored; RGB is treated as opaque. If colorType() is kAlpha_8_SkColorType, then RGB is ignored. Input color is ultimately converted to an SkColor4f, so erase(SkColor4f c) will have higher color resolution. @param c unpremultiplied color @param area rectangle to fill example: https://fiddle.skia.org/c/@Bitmap_erase */ void erase(SkColor c, const SkIRect& area) const; /** Deprecated. */ void eraseArea(const SkIRect& area, SkColor c) const { this->erase(c, area); } /** Returns pixel at (x, y) as unpremultiplied color. Returns black with alpha if SkColorType is kAlpha_8_SkColorType. Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined; and returns undefined values or may crash if SK_RELEASE is defined. Fails if SkColorType is kUnknown_SkColorType or pixel address is nullptr. SkColorSpace in SkImageInfo is ignored. Some color precision may be lost in the conversion to unpremultiplied color; original pixel data may have additional precision. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return pixel converted to unpremultiplied color */ SkColor getColor(int x, int y) const { return this->pixmap().getColor(x, y); } /** Returns pixel at (x, y) as unpremultiplied float color. Returns black with alpha if SkColorType is kAlpha_8_SkColorType. Input is not validated: out of bounds values of x or y trigger an assert() if built with SK_DEBUG defined; and returns undefined values or may crash if SK_RELEASE is defined. Fails if SkColorType is kUnknown_SkColorType or pixel address is nullptr. SkColorSpace in SkImageInfo is ignored. Some color precision may be lost in the conversion to unpremultiplied color. @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return pixel converted to unpremultiplied color */ SkColor4f getColor4f(int x, int y) const { return this->pixmap().getColor4f(x, y); } /** Look up the pixel at (x,y) and return its alpha component, normalized to [0..1]. This is roughly equivalent to SkGetColorA(getColor()), but can be more efficent (and more precise if the pixels store more than 8 bits per component). @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return alpha converted to normalized float */ float getAlphaf(int x, int y) const { return this->pixmap().getAlphaf(x, y); } /** Returns pixel address at (x, y). Input is not validated: out of bounds values of x or y, or kUnknown_SkColorType, trigger an assert() if built with SK_DEBUG defined. Returns nullptr if SkColorType is kUnknown_SkColorType, or SkPixelRef is nullptr. Performs a lookup of pixel size; for better performance, call one of: getAddr8(), getAddr16(), or getAddr32(). @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return generic pointer to pixel example: https://fiddle.skia.org/c/@Bitmap_getAddr */ void* getAddr(int x, int y) const; /** Returns address at (x, y). Input is not validated. Triggers an assert() if built with SK_DEBUG defined and: - SkPixelRef is nullptr - bytesPerPixel() is not four - x is negative, or not less than width() - y is negative, or not less than height() @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return unsigned 32-bit pointer to pixel at (x, y) */ inline uint32_t* getAddr32(int x, int y) const; /** Returns address at (x, y). Input is not validated. Triggers an assert() if built with SK_DEBUG defined and: - SkPixelRef is nullptr - bytesPerPixel() is not two - x is negative, or not less than width() - y is negative, or not less than height() @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return unsigned 16-bit pointer to pixel at (x, y) */ inline uint16_t* getAddr16(int x, int y) const; /** Returns address at (x, y). Input is not validated. Triggers an assert() if built with SK_DEBUG defined and: - SkPixelRef is nullptr - bytesPerPixel() is not one - x is negative, or not less than width() - y is negative, or not less than height() @param x column index, zero or greater, and less than width() @param y row index, zero or greater, and less than height() @return unsigned 8-bit pointer to pixel at (x, y) */ inline uint8_t* getAddr8(int x, int y) const; /** Shares SkPixelRef with dst. Pixels are not copied; SkBitmap and dst point to the same pixels; dst bounds() are set to the intersection of subset and the original bounds(). subset may be larger than bounds(). Any area outside of bounds() is ignored. Any contents of dst are discarded. Return false if: - dst is nullptr - SkPixelRef is nullptr - subset does not intersect bounds() @param dst SkBitmap set to subset @param subset rectangle of pixels to reference @return true if dst is replaced by subset example: https://fiddle.skia.org/c/@Bitmap_extractSubset */ bool extractSubset(SkBitmap* dst, const SkIRect& subset) const; /** Copies a SkRect of pixels from SkBitmap to dstPixels. Copy starts at (srcX, srcY), and does not exceed SkBitmap (width(), height()). dstInfo specifies width, height, SkColorType, SkAlphaType, and SkColorSpace of destination. dstRowBytes specifics the gap from one destination row to the next. Returns true if pixels are copied. Returns false if: - dstInfo has no address - dstRowBytes is less than dstInfo.minRowBytes() - SkPixelRef is nullptr Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dstInfo.colorType() must match. If SkBitmap colorType() is kGray_8_SkColorType, dstInfo.colorSpace() must match. If SkBitmap alphaType() is kOpaque_SkAlphaType, dstInfo.alphaType() must match. If SkBitmap colorSpace() is nullptr, dstInfo.colorSpace() must match. Returns false if pixel conversion is not possible. srcX and srcY may be negative to copy only top or left of source. Returns false if width() or height() is zero or negative. Returns false if abs(srcX) >= Bitmap width(), or if abs(srcY) >= Bitmap height(). @param dstInfo destination width, height, SkColorType, SkAlphaType, SkColorSpace @param dstPixels destination pixel storage @param dstRowBytes destination row length @param srcX column index whose absolute value is less than width() @param srcY row index whose absolute value is less than height() @return true if pixels are copied to dstPixels */ bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY) const; /** Copies a SkRect of pixels from SkBitmap to dst. Copy starts at (srcX, srcY), and does not exceed SkBitmap (width(), height()). dst specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, and row bytes of destination. dst.rowBytes() specifics the gap from one destination row to the next. Returns true if pixels are copied. Returns false if: - dst pixel storage equals nullptr - dst.rowBytes is less than SkImageInfo::minRowBytes() - SkPixelRef is nullptr Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. If SkBitmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. If SkBitmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must match. If SkBitmap colorSpace() is nullptr, dst SkColorSpace must match. Returns false if pixel conversion is not possible. srcX and srcY may be negative to copy only top or left of source. Returns false if width() or height() is zero or negative. Returns false if abs(srcX) >= Bitmap width(), or if abs(srcY) >= Bitmap height(). @param dst destination SkPixmap: SkImageInfo, pixels, row bytes @param srcX column index whose absolute value is less than width() @param srcY row index whose absolute value is less than height() @return true if pixels are copied to dst example: https://fiddle.skia.org/c/@Bitmap_readPixels_2 */ bool readPixels(const SkPixmap& dst, int srcX, int srcY) const; /** Copies a SkRect of pixels from SkBitmap to dst. Copy starts at (0, 0), and does not exceed SkBitmap (width(), height()). dst specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, and row bytes of destination. dst.rowBytes() specifics the gap from one destination row to the next. Returns true if pixels are copied. Returns false if: - dst pixel storage equals nullptr - dst.rowBytes is less than SkImageInfo::minRowBytes() - SkPixelRef is nullptr Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; dst SkColorType must match. If SkBitmap colorType() is kGray_8_SkColorType, dst SkColorSpace must match. If SkBitmap alphaType() is kOpaque_SkAlphaType, dst SkAlphaType must match. If SkBitmap colorSpace() is nullptr, dst SkColorSpace must match. Returns false if pixel conversion is not possible. @param dst destination SkPixmap: SkImageInfo, pixels, row bytes @return true if pixels are copied to dst */ bool readPixels(const SkPixmap& dst) const { return this->readPixels(dst, 0, 0); } /** Copies a SkRect of pixels from src. Copy starts at (dstX, dstY), and does not exceed (src.width(), src.height()). src specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, and row bytes of source. src.rowBytes() specifics the gap from one source row to the next. Returns true if pixels are copied. Returns false if: - src pixel storage equals nullptr - src.rowBytes is less than SkImageInfo::minRowBytes() - SkPixelRef is nullptr Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; src SkColorType must match. If SkBitmap colorType() is kGray_8_SkColorType, src SkColorSpace must match. If SkBitmap alphaType() is kOpaque_SkAlphaType, src SkAlphaType must match. If SkBitmap colorSpace() is nullptr, src SkColorSpace must match. Returns false if pixel conversion is not possible. dstX and dstY may be negative to copy only top or left of source. Returns false if width() or height() is zero or negative. Returns false if abs(dstX) >= Bitmap width(), or if abs(dstY) >= Bitmap height(). @param src source SkPixmap: SkImageInfo, pixels, row bytes @param dstX column index whose absolute value is less than width() @param dstY row index whose absolute value is less than height() @return true if src pixels are copied to SkBitmap example: https://fiddle.skia.org/c/@Bitmap_writePixels */ bool writePixels(const SkPixmap& src, int dstX, int dstY); /** Copies a SkRect of pixels from src. Copy starts at (0, 0), and does not exceed (src.width(), src.height()). src specifies width, height, SkColorType, SkAlphaType, SkColorSpace, pixel storage, and row bytes of source. src.rowBytes() specifics the gap from one source row to the next. Returns true if pixels are copied. Returns false if: - src pixel storage equals nullptr - src.rowBytes is less than SkImageInfo::minRowBytes() - SkPixelRef is nullptr Pixels are copied only if pixel conversion is possible. If SkBitmap colorType() is kGray_8_SkColorType, or kAlpha_8_SkColorType; src SkColorType must match. If SkBitmap colorType() is kGray_8_SkColorType, src SkColorSpace must match. If SkBitmap alphaType() is kOpaque_SkAlphaType, src SkAlphaType must match. If SkBitmap colorSpace() is nullptr, src SkColorSpace must match. Returns false if pixel conversion is not possible. @param src source SkPixmap: SkImageInfo, pixels, row bytes @return true if src pixels are copied to SkBitmap */ bool writePixels(const SkPixmap& src) { return this->writePixels(src, 0, 0); } /** Sets dst to alpha described by pixels. Returns false if dst cannot be written to or dst pixels cannot be allocated. Uses HeapAllocator to reserve memory for dst SkPixelRef. @param dst holds SkPixelRef to fill with alpha layer @return true if alpha layer was constructed in dst SkPixelRef */ bool extractAlpha(SkBitmap* dst) const { return this->extractAlpha(dst, nullptr, nullptr, nullptr); } /** Sets dst to alpha described by pixels. Returns false if dst cannot be written to or dst pixels cannot be allocated. If paint is not nullptr and contains SkMaskFilter, SkMaskFilter generates mask alpha from SkBitmap. Uses HeapAllocator to reserve memory for dst SkPixelRef. Sets offset to top-left position for dst for alignment with SkBitmap; (0, 0) unless SkMaskFilter generates mask. @param dst holds SkPixelRef to fill with alpha layer @param paint holds optional SkMaskFilter; may be nullptr @param offset top-left position for dst; may be nullptr @return true if alpha layer was constructed in dst SkPixelRef */ bool extractAlpha(SkBitmap* dst, const SkPaint* paint, SkIPoint* offset) const { return this->extractAlpha(dst, paint, nullptr, offset); } /** Sets dst to alpha described by pixels. Returns false if dst cannot be written to or dst pixels cannot be allocated. If paint is not nullptr and contains SkMaskFilter, SkMaskFilter generates mask alpha from SkBitmap. allocator may reference a custom allocation class or be set to nullptr to use HeapAllocator. Sets offset to top-left position for dst for alignment with SkBitmap; (0, 0) unless SkMaskFilter generates mask. @param dst holds SkPixelRef to fill with alpha layer @param paint holds optional SkMaskFilter; may be nullptr @param allocator function to reserve memory for SkPixelRef; may be nullptr @param offset top-left position for dst; may be nullptr @return true if alpha layer was constructed in dst SkPixelRef */ bool extractAlpha(SkBitmap* dst, const SkPaint* paint, Allocator* allocator, SkIPoint* offset) const; /** Copies SkBitmap pixel address, row bytes, and SkImageInfo to pixmap, if address is available, and returns true. If pixel address is not available, return false and leave pixmap unchanged. pixmap contents become invalid on any future change to SkBitmap. @param pixmap storage for pixel state if pixels are readable; otherwise, ignored @return true if SkBitmap has direct access to pixels example: https://fiddle.skia.org/c/@Bitmap_peekPixels */ bool peekPixels(SkPixmap* pixmap) const; /** * Make a shader with the specified tiling, matrix and sampling. */ sk_sp makeShader(SkTileMode tmx, SkTileMode tmy, const SkSamplingOptions&, const SkMatrix* localMatrix = nullptr) const; sk_sp makeShader(SkTileMode tmx, SkTileMode tmy, const SkSamplingOptions& sampling, const SkMatrix& lm) const; /** Defaults to clamp in both X and Y. */ sk_sp makeShader(const SkSamplingOptions& sampling, const SkMatrix& lm) const; sk_sp makeShader(const SkSamplingOptions& sampling, const SkMatrix* lm = nullptr) const; /** * Returns a new image from the bitmap. If the bitmap is marked immutable, this will * share the pixel buffer. If not, it will make a copy of the pixels for the image. */ sk_sp asImage() const; /** Asserts if internal values are illegal or inconsistent. Only available if SK_DEBUG is defined at compile time. */ SkDEBUGCODE(void validate() const;) /** \class SkBitmap::Allocator Abstract subclass of HeapAllocator. */ class Allocator : public SkRefCnt { public: /** Allocates the pixel memory for the bitmap, given its dimensions and SkColorType. Returns true on success, where success means either setPixels() or setPixelRef() was called. @param bitmap SkBitmap containing SkImageInfo as input, and SkPixelRef as output @return true if SkPixelRef was allocated */ virtual bool allocPixelRef(SkBitmap* bitmap) = 0; private: using INHERITED = SkRefCnt; }; /** \class SkBitmap::HeapAllocator Subclass of SkBitmap::Allocator that returns a SkPixelRef that allocates its pixel memory from the heap. This is the default SkBitmap::Allocator invoked by allocPixels(). */ class HeapAllocator : public Allocator { public: /** Allocates the pixel memory for the bitmap, given its dimensions and SkColorType. Returns true on success, where success means either setPixels() or setPixelRef() was called. @param bitmap SkBitmap containing SkImageInfo as input, and SkPixelRef as output @return true if pixels are allocated example: https://fiddle.skia.org/c/@Bitmap_HeapAllocator_allocPixelRef */ bool allocPixelRef(SkBitmap* bitmap) override; }; private: sk_sp fPixelRef; SkPixmap fPixmap; sk_sp fMips; friend class SkImage_Raster; friend class SkReadBuffer; // unflatten friend class GrProxyProvider; // fMips }; /////////////////////////////////////////////////////////////////////////////// inline uint32_t* SkBitmap::getAddr32(int x, int y) const { SkASSERT(fPixmap.addr()); return fPixmap.writable_addr32(x, y); } inline uint16_t* SkBitmap::getAddr16(int x, int y) const { SkASSERT(fPixmap.addr()); return fPixmap.writable_addr16(x, y); } inline uint8_t* SkBitmap::getAddr8(int x, int y) const { SkASSERT(fPixmap.addr()); return fPixmap.writable_addr8(x, y); } /** * Blends are operators that take in two colors (source, destination) and return a new color. * Many of these operate the same on all 4 components: red, green, blue, alpha. For these, * we just document what happens to one component, rather than naming each one separately. * * Different SkColorTypes have different representations for color components: * 8-bit: 0..255 * 6-bit: 0..63 * 5-bit: 0..31 * 4-bit: 0..15 * floats: 0...1 * * The documentation is expressed as if the component values are always 0..1 (floats). * * For brevity, the documentation uses the following abbreviations * s : source * d : destination * sa : source alpha * da : destination alpha * * Results are abbreviated * r : if all 4 components are computed in the same manner * ra : result alpha component * rc : result "color": red, green, blue components */ enum class SkBlendMode { kClear, //!< r = 0 kSrc, //!< r = s kDst, //!< r = d kSrcOver, //!< r = s + (1-sa)*d kDstOver, //!< r = d + (1-da)*s kSrcIn, //!< r = s * da kDstIn, //!< r = d * sa kSrcOut, //!< r = s * (1-da) kDstOut, //!< r = d * (1-sa) kSrcATop, //!< r = s*da + d*(1-sa) kDstATop, //!< r = d*sa + s*(1-da) kXor, //!< r = s*(1-da) + d*(1-sa) kPlus, //!< r = min(s + d, 1) kModulate, //!< r = s*d kScreen, //!< r = s + d - s*d kOverlay, //!< multiply or screen, depending on destination kDarken, //!< rc = s + d - max(s*da, d*sa), ra = kSrcOver kLighten, //!< rc = s + d - min(s*da, d*sa), ra = kSrcOver kColorDodge, //!< brighten destination to reflect source kColorBurn, //!< darken destination to reflect source kHardLight, //!< multiply or screen, depending on source kSoftLight, //!< lighten or darken, depending on source kDifference, //!< rc = s + d - 2*(min(s*da, d*sa)), ra = kSrcOver kExclusion, //!< rc = s + d - two(s*d), ra = kSrcOver kMultiply, //!< r = s*(1-da) + d*(1-sa) + s*d kHue, //!< hue of source with saturation and luminosity of destination kSaturation, //!< saturation of source with hue and luminosity of destination kColor, //!< hue and saturation of source with luminosity of destination kLuminosity, //!< luminosity of source with hue and saturation of destination kLastCoeffMode = kScreen, //!< last porter duff blend mode kLastSeparableMode = kMultiply, //!< last blend mode operating separately on components kLastMode = kLuminosity, //!< last valid value }; static constexpr int kSkBlendModeCount = static_cast(SkBlendMode::kLastMode) + 1; /** * For Porter-Duff SkBlendModes (those <= kLastCoeffMode), these coefficients describe the blend * equation used. Coefficient-based blend modes specify an equation: * ('dstCoeff' * dst + 'srcCoeff' * src), where the coefficient values are constants, functions of * the src or dst alpha, or functions of the src or dst color. */ enum class SkBlendModeCoeff { kZero, /** 0 */ kOne, /** 1 */ kSC, /** src color */ kISC, /** inverse src color (i.e. 1 - sc) */ kDC, /** dst color */ kIDC, /** inverse dst color (i.e. 1 - dc) */ kSA, /** src alpha */ kISA, /** inverse src alpha (i.e. 1 - sa) */ kDA, /** dst alpha */ kIDA, /** inverse dst alpha (i.e. 1 - da) */ kCoeffCount }; /** * Returns true if 'mode' is a coefficient-based blend mode (<= kLastCoeffMode). If true is * returned, the mode's src and dst coefficient functions are set in 'src' and 'dst'. */ SK_API bool SkBlendMode_AsCoeff(SkBlendMode mode, SkBlendModeCoeff* src, SkBlendModeCoeff* dst); /** Returns name of blendMode as null-terminated C string. @return C string */ SK_API const char* SkBlendMode_Name(SkBlendMode blendMode); enum class SkClipOp { kDifference = 0, kIntersect = 1, kMax_EnumValue = kIntersect }; enum class SkTextEncoding { kUTF8, //!< uses bytes to represent UTF-8 or ASCII kUTF16, //!< uses two byte words to represent most of Unicode kUTF32, //!< uses four byte words to represent all of Unicode kGlyphID, //!< uses two byte words to represent glyph indices }; enum class SkFontHinting { kNone, //!< glyph outlines unchanged kSlight, //!< minimal modification to improve constrast kNormal, //!< glyph outlines modified to improve constrast kFull, //!< modifies glyph outlines for maximum constrast }; class SkData; class SkReadBuffer; class SkWriteBuffer; struct SkDeserialProcs; struct SkSerialProcs; /** \class SkFlattenable SkFlattenable is the base class for objects that need to be flattened into a data stream for either transport or as part of the key to the font cache. */ class SK_API SkFlattenable : public SkRefCnt { public: enum Type { kSkColorFilter_Type, kSkBlender_Type, kSkDrawable_Type, kSkDrawLooper_Type, // no longer used internally by Skia kSkImageFilter_Type, kSkMaskFilter_Type, kSkPathEffect_Type, kSkShader_Type, }; typedef sk_sp (*Factory)(SkReadBuffer&); SkFlattenable() {} /** Implement this to return a factory function pointer that can be called to recreate your class given a buffer (previously written to by your override of flatten(). */ virtual Factory getFactory() const = 0; /** * Returns the name of the object's class. */ virtual const char* getTypeName() const = 0; static Factory NameToFactory(const char name[]); static const char* FactoryToName(Factory); static void Register(const char name[], Factory); /** * Override this if your subclass needs to record data that it will need to recreate itself * from its CreateProc (returned by getFactory()). * * DEPRECATED public : will move to protected ... use serialize() instead */ virtual void flatten(SkWriteBuffer&) const {} virtual Type getFlattenableType() const = 0; // // public ways to serialize / deserialize // sk_sp serialize(const SkSerialProcs* = nullptr) const; size_t serialize(void* memory, size_t memory_size, const SkSerialProcs* = nullptr) const; static sk_sp Deserialize(Type, const void* data, size_t length, const SkDeserialProcs* procs = nullptr); protected: class PrivateInitializer { public: static void InitEffects(); static void InitImageFilters(); }; private: static void RegisterFlattenablesIfNeeded(); static void Finalize(); friend class SkGraphics; using INHERITED = SkRefCnt; }; #if defined(SK_DISABLE_EFFECT_DESERIALIZATION) #define SK_REGISTER_FLATTENABLE(type) do{}while(false) #define SK_FLATTENABLE_HOOKS(type) \ static sk_sp CreateProc(SkReadBuffer&); \ friend class SkFlattenable::PrivateInitializer; \ Factory getFactory() const override { return nullptr; } \ const char* getTypeName() const override { return #type; } #else #define SK_REGISTER_FLATTENABLE(type) \ SkFlattenable::Register(#type, type::CreateProc) #define SK_FLATTENABLE_HOOKS(type) \ static sk_sp CreateProc(SkReadBuffer&); \ friend class SkFlattenable::PrivateInitializer; \ Factory getFactory() const override { return type::CreateProc; } \ const char* getTypeName() const override { return #type; } #endif class SkColorFilter; class SkMatrix; struct SkDeserialProcs; /** * Base class for image filters. If one is installed in the paint, then all drawing occurs as * usual, but it is as if the drawing happened into an offscreen (before the xfermode is applied). * This offscreen bitmap will then be handed to the imagefilter, who in turn creates a new bitmap * which is what will finally be drawn to the device (using the original xfermode). * * The local space of image filters matches the local space of the drawn geometry. For instance if * there is rotation on the canvas, the blur will be computed along those rotated axes and not in * the device space. In order to achieve this result, the actual drawing of the geometry may happen * in an unrotated coordinate system so that the filtered image can be computed more easily, and * then it will be post transformed to match what would have been produced if the geometry were * drawn with the total canvas matrix to begin with. */ class SK_API SkImageFilter : public SkFlattenable { public: enum MapDirection { kForward_MapDirection, kReverse_MapDirection, }; /** * Map a device-space rect recursively forward or backward through the filter DAG. * kForward_MapDirection is used to determine which pixels of the destination canvas a source * image rect would touch after filtering. kReverse_MapDirection is used to determine which rect * of the source image would be required to fill the given rect (typically, clip bounds). Used * for clipping and temp-buffer allocations, so the result need not be exact, but should never * be smaller than the real answer. The default implementation recursively unions all input * bounds, or returns the source rect if no inputs. * * In kReverse mode, 'inputRect' is the device-space bounds of the input pixels. In kForward * mode it should always be null. If 'inputRect' is null in kReverse mode the resulting answer * may be incorrect. */ SkIRect filterBounds(const SkIRect& src, const SkMatrix& ctm, MapDirection, const SkIRect* inputRect = nullptr) const; /** * Returns whether this image filter is a color filter and puts the color filter into the * "filterPtr" parameter if it can. Does nothing otherwise. * If this returns false, then the filterPtr is unchanged. * If this returns true, then if filterPtr is not null, it must be set to a ref'd colorfitler * (i.e. it may not be set to NULL). */ bool isColorFilterNode(SkColorFilter** filterPtr) const; // DEPRECATED : use isColorFilterNode() instead bool asColorFilter(SkColorFilter** filterPtr) const { return this->isColorFilterNode(filterPtr); } /** * Returns true (and optionally returns a ref'd filter) if this imagefilter can be completely * replaced by the returned colorfilter. i.e. the two effects will affect drawing in the same * way. */ bool asAColorFilter(SkColorFilter** filterPtr) const; /** * Returns the number of inputs this filter will accept (some inputs can be NULL). */ int countInputs() const; /** * Returns the input filter at a given index, or NULL if no input is connected. The indices * used are filter-specific. */ const SkImageFilter* getInput(int i) const; // Default impl returns union of all input bounds. virtual SkRect computeFastBounds(const SkRect& bounds) const; // Can this filter DAG compute the resulting bounds of an object-space rectangle? bool canComputeFastBounds() const; /** * If this filter can be represented by another filter + a localMatrix, return that filter, * else return null. */ sk_sp makeWithLocalMatrix(const SkMatrix& matrix) const; static sk_sp Deserialize(const void* data, size_t size, const SkDeserialProcs* procs = nullptr) { return sk_sp(static_cast( SkFlattenable::Deserialize(kSkImageFilter_Type, data, size, procs).release())); } protected: sk_sp refMe() const { return sk_ref_sp(const_cast(this)); } private: friend class SkImageFilter_Base; using INHERITED = SkFlattenable; }; struct SkRect; struct SK_API SkV2 { float x, y; bool operator==(const SkV2 v) const { return x == v.x && y == v.y; } bool operator!=(const SkV2 v) const { return !(*this == v); } static SkScalar Dot(SkV2 a, SkV2 b) { return a.x * b.x + a.y * b.y; } static SkScalar Cross(SkV2 a, SkV2 b) { return a.x * b.y - a.y * b.x; } static SkV2 Normalize(SkV2 v) { return v * (1.0f / v.length()); } SkV2 operator-() const { return {-x, -y}; } SkV2 operator+(SkV2 v) const { return {x+v.x, y+v.y}; } SkV2 operator-(SkV2 v) const { return {x-v.x, y-v.y}; } SkV2 operator*(SkV2 v) const { return {x*v.x, y*v.y}; } friend SkV2 operator*(SkV2 v, SkScalar s) { return {v.x*s, v.y*s}; } friend SkV2 operator*(SkScalar s, SkV2 v) { return {v.x*s, v.y*s}; } friend SkV2 operator/(SkV2 v, SkScalar s) { return {v.x/s, v.y/s}; } friend SkV2 operator/(SkScalar s, SkV2 v) { return {s/v.x, s/v.y}; } void operator+=(SkV2 v) { *this = *this + v; } void operator-=(SkV2 v) { *this = *this - v; } void operator*=(SkV2 v) { *this = *this * v; } void operator*=(SkScalar s) { *this = *this * s; } void operator/=(SkScalar s) { *this = *this / s; } SkScalar lengthSquared() const { return Dot(*this, *this); } SkScalar length() const { return SkScalarSqrt(this->lengthSquared()); } SkScalar dot(SkV2 v) const { return Dot(*this, v); } SkScalar cross(SkV2 v) const { return Cross(*this, v); } SkV2 normalize() const { return Normalize(*this); } const float* ptr() const { return &x; } float* ptr() { return &x; } }; struct SK_API SkV3 { float x, y, z; bool operator==(const SkV3& v) const { return x == v.x && y == v.y && z == v.z; } bool operator!=(const SkV3& v) const { return !(*this == v); } static SkScalar Dot(const SkV3& a, const SkV3& b) { return a.x*b.x + a.y*b.y + a.z*b.z; } static SkV3 Cross(const SkV3& a, const SkV3& b) { return { a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x }; } static SkV3 Normalize(const SkV3& v) { return v * (1.0f / v.length()); } SkV3 operator-() const { return {-x, -y, -z}; } SkV3 operator+(const SkV3& v) const { return { x + v.x, y + v.y, z + v.z }; } SkV3 operator-(const SkV3& v) const { return { x - v.x, y - v.y, z - v.z }; } SkV3 operator*(const SkV3& v) const { return { x*v.x, y*v.y, z*v.z }; } friend SkV3 operator*(const SkV3& v, SkScalar s) { return { v.x*s, v.y*s, v.z*s }; } friend SkV3 operator*(SkScalar s, const SkV3& v) { return v*s; } void operator+=(SkV3 v) { *this = *this + v; } void operator-=(SkV3 v) { *this = *this - v; } void operator*=(SkV3 v) { *this = *this * v; } void operator*=(SkScalar s) { *this = *this * s; } SkScalar lengthSquared() const { return Dot(*this, *this); } SkScalar length() const { return SkScalarSqrt(Dot(*this, *this)); } SkScalar dot(const SkV3& v) const { return Dot(*this, v); } SkV3 cross(const SkV3& v) const { return Cross(*this, v); } SkV3 normalize() const { return Normalize(*this); } const float* ptr() const { return &x; } float* ptr() { return &x; } }; struct SK_API SkV4 { float x, y, z, w; bool operator==(const SkV4& v) const { return x == v.x && y == v.y && z == v.z && w == v.w; } bool operator!=(const SkV4& v) const { return !(*this == v); } static SkScalar Dot(const SkV4& a, const SkV4& b) { return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; } static SkV4 Normalize(const SkV4& v) { return v * (1.0f / v.length()); } SkV4 operator-() const { return {-x, -y, -z, -w}; } SkV4 operator+(const SkV4& v) const { return { x + v.x, y + v.y, z + v.z, w + v.w }; } SkV4 operator-(const SkV4& v) const { return { x - v.x, y - v.y, z - v.z, w - v.w }; } SkV4 operator*(const SkV4& v) const { return { x*v.x, y*v.y, z*v.z, w*v.w }; } friend SkV4 operator*(const SkV4& v, SkScalar s) { return { v.x*s, v.y*s, v.z*s, v.w*s }; } friend SkV4 operator*(SkScalar s, const SkV4& v) { return v*s; } SkScalar lengthSquared() const { return Dot(*this, *this); } SkScalar length() const { return SkScalarSqrt(Dot(*this, *this)); } SkScalar dot(const SkV4& v) const { return Dot(*this, v); } SkV4 normalize() const { return Normalize(*this); } const float* ptr() const { return &x; } float* ptr() { return &x; } float operator[](int i) const { SkASSERT(i >= 0 && i < 4); return this->ptr()[i]; } float& operator[](int i) { SkASSERT(i >= 0 && i < 4); return this->ptr()[i]; } }; /** * 4x4 matrix used by SkCanvas and other parts of Skia. * * Skia assumes a right-handed coordinate system: * +X goes to the right * +Y goes down * +Z goes into the screen (away from the viewer) */ class SK_API SkM44 { public: SkM44(const SkM44& src) = default; SkM44& operator=(const SkM44& src) = default; constexpr SkM44() : fMat{1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1} {} SkM44(const SkM44& a, const SkM44& b) { this->setConcat(a, b); } enum Uninitialized_Constructor { kUninitialized_Constructor }; SkM44(Uninitialized_Constructor) {} enum NaN_Constructor { kNaN_Constructor }; constexpr SkM44(NaN_Constructor) : fMat{SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN, SK_ScalarNaN} {} /** * The constructor parameters are in row-major order. */ constexpr SkM44(SkScalar m0, SkScalar m4, SkScalar m8, SkScalar m12, SkScalar m1, SkScalar m5, SkScalar m9, SkScalar m13, SkScalar m2, SkScalar m6, SkScalar m10, SkScalar m14, SkScalar m3, SkScalar m7, SkScalar m11, SkScalar m15) // fMat is column-major order in memory. : fMat{m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15} {} static SkM44 Rows(const SkV4& r0, const SkV4& r1, const SkV4& r2, const SkV4& r3) { SkM44 m(kUninitialized_Constructor); m.setRow(0, r0); m.setRow(1, r1); m.setRow(2, r2); m.setRow(3, r3); return m; } static SkM44 Cols(const SkV4& c0, const SkV4& c1, const SkV4& c2, const SkV4& c3) { SkM44 m(kUninitialized_Constructor); m.setCol(0, c0); m.setCol(1, c1); m.setCol(2, c2); m.setCol(3, c3); return m; } static SkM44 RowMajor(const SkScalar r[16]) { return SkM44(r[ 0], r[ 1], r[ 2], r[ 3], r[ 4], r[ 5], r[ 6], r[ 7], r[ 8], r[ 9], r[10], r[11], r[12], r[13], r[14], r[15]); } static SkM44 ColMajor(const SkScalar c[16]) { return SkM44(c[0], c[4], c[ 8], c[12], c[1], c[5], c[ 9], c[13], c[2], c[6], c[10], c[14], c[3], c[7], c[11], c[15]); } static SkM44 Translate(SkScalar x, SkScalar y, SkScalar z = 0) { return SkM44(1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1); } static SkM44 Scale(SkScalar x, SkScalar y, SkScalar z = 1) { return SkM44(x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1); } static SkM44 Rotate(SkV3 axis, SkScalar radians) { SkM44 m(kUninitialized_Constructor); m.setRotate(axis, radians); return m; } // Scales and translates 'src' to fill 'dst' exactly. static SkM44 RectToRect(const SkRect& src, const SkRect& dst); static SkM44 LookAt(const SkV3& eye, const SkV3& center, const SkV3& up); static SkM44 Perspective(float near, float far, float angle); bool operator==(const SkM44& other) const; bool operator!=(const SkM44& other) const { return !(other == *this); } void getColMajor(SkScalar v[]) const { memcpy(v, fMat, sizeof(fMat)); } void getRowMajor(SkScalar v[]) const; SkScalar rc(int r, int c) const { SkASSERT(r >= 0 && r <= 3); SkASSERT(c >= 0 && c <= 3); return fMat[c*4 + r]; } void setRC(int r, int c, SkScalar value) { SkASSERT(r >= 0 && r <= 3); SkASSERT(c >= 0 && c <= 3); fMat[c*4 + r] = value; } SkV4 row(int i) const { SkASSERT(i >= 0 && i <= 3); return {fMat[i + 0], fMat[i + 4], fMat[i + 8], fMat[i + 12]}; } SkV4 col(int i) const { SkASSERT(i >= 0 && i <= 3); return {fMat[i*4 + 0], fMat[i*4 + 1], fMat[i*4 + 2], fMat[i*4 + 3]}; } void setRow(int i, const SkV4& v) { SkASSERT(i >= 0 && i <= 3); fMat[i + 0] = v.x; fMat[i + 4] = v.y; fMat[i + 8] = v.z; fMat[i + 12] = v.w; } void setCol(int i, const SkV4& v) { SkASSERT(i >= 0 && i <= 3); memcpy(&fMat[i*4], v.ptr(), sizeof(v)); } SkM44& setIdentity() { *this = { 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 }; return *this; } SkM44& setTranslate(SkScalar x, SkScalar y, SkScalar z = 0) { *this = { 1, 0, 0, x, 0, 1, 0, y, 0, 0, 1, z, 0, 0, 0, 1 }; return *this; } SkM44& setScale(SkScalar x, SkScalar y, SkScalar z = 1) { *this = { x, 0, 0, 0, 0, y, 0, 0, 0, 0, z, 0, 0, 0, 0, 1 }; return *this; } /** * Set this matrix to rotate about the specified unit-length axis vector, * by an angle specified by its sin() and cos(). * * This does not attempt to verify that axis.length() == 1 or that the sin,cos values * are correct. */ SkM44& setRotateUnitSinCos(SkV3 axis, SkScalar sinAngle, SkScalar cosAngle); /** * Set this matrix to rotate about the specified unit-length axis vector, * by an angle specified in radians. * * This does not attempt to verify that axis.length() == 1. */ SkM44& setRotateUnit(SkV3 axis, SkScalar radians) { return this->setRotateUnitSinCos(axis, SkScalarSin(radians), SkScalarCos(radians)); } /** * Set this matrix to rotate about the specified axis vector, * by an angle specified in radians. * * Note: axis is not assumed to be unit-length, so it will be normalized internally. * If axis is already unit-length, call setRotateAboutUnitRadians() instead. */ SkM44& setRotate(SkV3 axis, SkScalar radians); SkM44& setConcat(const SkM44& a, const SkM44& b); friend SkM44 operator*(const SkM44& a, const SkM44& b) { return SkM44(a, b); } SkM44& preConcat(const SkM44& m) { return this->setConcat(*this, m); } SkM44& postConcat(const SkM44& m) { return this->setConcat(m, *this); } /** * A matrix is categorized as 'perspective' if the bottom row is not [0, 0, 0, 1]. * For most uses, a bottom row of [0, 0, 0, X] behaves like a non-perspective matrix, though * it will be categorized as perspective. Calling normalizePerspective() will change the * matrix such that, if its bottom row was [0, 0, 0, X], it will be changed to [0, 0, 0, 1] * by scaling the rest of the matrix by 1/X. * * | A B C D | | A/X B/X C/X D/X | * | E F G H | -> | E/X F/X G/X H/X | for X != 0 * | I J K L | | I/X J/X K/X L/X | * | 0 0 0 X | | 0 0 0 1 | */ void normalizePerspective(); /** Returns true if all elements of the matrix are finite. Returns false if any element is infinity, or NaN. @return true if matrix has only finite elements */ bool isFinite() const { return SkScalarsAreFinite(fMat, 16); } /** If this is invertible, return that in inverse and return true. If it is * not invertible, return false and leave the inverse parameter unchanged. */ [[nodiscard]] bool invert(SkM44* inverse) const; [[nodiscard]] SkM44 transpose() const; void dump() const; //////////// SkV4 map(float x, float y, float z, float w) const; SkV4 operator*(const SkV4& v) const { return this->map(v.x, v.y, v.z, v.w); } SkV3 operator*(SkV3 v) const { auto v4 = this->map(v.x, v.y, v.z, 0); return {v4.x, v4.y, v4.z}; } ////////////////////// Converting to/from SkMatrix /* When converting from SkM44 to SkMatrix, the third row and * column is dropped. When converting from SkMatrix to SkM44 * the third row and column remain as identity: * [ a b c ] [ a b 0 c ] * [ d e f ] -> [ d e 0 f ] * [ g h i ] [ 0 0 1 0 ] * [ g h 0 i ] */ SkMatrix asM33() const { return SkMatrix::MakeAll(fMat[0], fMat[4], fMat[12], fMat[1], fMat[5], fMat[13], fMat[3], fMat[7], fMat[15]); } explicit SkM44(const SkMatrix& src) : SkM44(src[SkMatrix::kMScaleX], src[SkMatrix::kMSkewX], 0, src[SkMatrix::kMTransX], src[SkMatrix::kMSkewY], src[SkMatrix::kMScaleY], 0, src[SkMatrix::kMTransY], 0, 0, 1, 0, src[SkMatrix::kMPersp0], src[SkMatrix::kMPersp1], 0, src[SkMatrix::kMPersp2]) {} SkM44& preTranslate(SkScalar x, SkScalar y, SkScalar z = 0); SkM44& postTranslate(SkScalar x, SkScalar y, SkScalar z = 0); SkM44& preScale(SkScalar x, SkScalar y); SkM44& preScale(SkScalar x, SkScalar y, SkScalar z); SkM44& preConcat(const SkMatrix&); private: /* Stored in column-major. * Indices * 0 4 8 12 1 0 0 trans_x * 1 5 9 13 e.g. 0 1 0 trans_y * 2 6 10 14 0 0 1 trans_z * 3 7 11 15 0 0 0 1 */ SkScalar fMat[16]; friend class SkMatrixPriv; }; class SkBlender; class SkColorFilter; class SkColorSpace; class SkImageFilter; class SkMaskFilter; class SkPathEffect; class SkShader; enum class SkBlendMode; struct SkRect; /** \class SkPaint SkPaint controls options applied when drawing. SkPaint collects all options outside of the SkCanvas clip and SkCanvas matrix. Various options apply to strokes and fills, and images. SkPaint collects effects and filters that describe single-pass and multiple-pass algorithms that alter the drawing geometry, color, and transparency. For instance, SkPaint does not directly implement dashing or blur, but contains the objects that do so. */ class SK_API SkPaint { public: /** Constructs SkPaint with default values. @return default initialized SkPaint example: https://fiddle.skia.org/c/@Paint_empty_constructor */ SkPaint(); /** Constructs SkPaint with default values and the given color. Sets alpha and RGB used when stroking and filling. The color is four floating point values, unpremultiplied. The color values are interpreted as being in the colorSpace. If colorSpace is nullptr, then color is assumed to be in the sRGB color space. @param color unpremultiplied RGBA @param colorSpace SkColorSpace describing the encoding of color @return SkPaint with the given color */ explicit SkPaint(const SkColor4f& color, SkColorSpace* colorSpace = nullptr); /** Makes a shallow copy of SkPaint. SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, and SkImageFilter are shared between the original paint and the copy. Objects containing SkRefCnt increment their references by one. The referenced objects SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, and SkImageFilter cannot be modified after they are created. This prevents objects with SkRefCnt from being modified once SkPaint refers to them. @param paint original to copy @return shallow copy of paint example: https://fiddle.skia.org/c/@Paint_copy_const_SkPaint */ SkPaint(const SkPaint& paint); /** Implements a move constructor to avoid increasing the reference counts of objects referenced by the paint. After the call, paint is undefined, and can be safely destructed. @param paint original to move @return content of paint example: https://fiddle.skia.org/c/@Paint_move_SkPaint */ SkPaint(SkPaint&& paint); /** Decreases SkPaint SkRefCnt of owned objects: SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, and SkImageFilter. If the objects containing SkRefCnt go to zero, they are deleted. */ ~SkPaint(); /** Makes a shallow copy of SkPaint. SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, and SkImageFilter are shared between the original paint and the copy. Objects containing SkRefCnt in the prior destination are decreased by one, and the referenced objects are deleted if the resulting count is zero. Objects containing SkRefCnt in the parameter paint are increased by one. paint is unmodified. @param paint original to copy @return content of paint example: https://fiddle.skia.org/c/@Paint_copy_operator */ SkPaint& operator=(const SkPaint& paint); /** Moves the paint to avoid increasing the reference counts of objects referenced by the paint parameter. Objects containing SkRefCnt in the prior destination are decreased by one; those objects are deleted if the resulting count is zero. After the call, paint is undefined, and can be safely destructed. @param paint original to move @return content of paint example: https://fiddle.skia.org/c/@Paint_move_operator */ SkPaint& operator=(SkPaint&& paint); /** Compares a and b, and returns true if a and b are equivalent. May return false if SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, or SkImageFilter have identical contents but different pointers. @param a SkPaint to compare @param b SkPaint to compare @return true if SkPaint pair are equivalent */ SK_API friend bool operator==(const SkPaint& a, const SkPaint& b); /** Compares a and b, and returns true if a and b are not equivalent. May return true if SkPathEffect, SkShader, SkMaskFilter, SkColorFilter, or SkImageFilter have identical contents but different pointers. @param a SkPaint to compare @param b SkPaint to compare @return true if SkPaint pair are not equivalent */ friend bool operator!=(const SkPaint& a, const SkPaint& b) { return !(a == b); } /** Sets all SkPaint contents to their initial values. This is equivalent to replacing SkPaint with the result of SkPaint(). example: https://fiddle.skia.org/c/@Paint_reset */ void reset(); /** Returns true if pixels on the active edges of SkPath may be drawn with partial transparency. @return antialiasing state */ bool isAntiAlias() const { return SkToBool(fBitfields.fAntiAlias); } /** Requests, but does not require, that edge pixels draw opaque or with partial transparency. @param aa setting for antialiasing */ void setAntiAlias(bool aa) { fBitfields.fAntiAlias = static_cast(aa); } /** Returns true if color error may be distributed to smooth color transition. @return dithering state */ bool isDither() const { return SkToBool(fBitfields.fDither); } /** Requests, but does not require, to distribute color error. @param dither setting for ditering */ void setDither(bool dither) { fBitfields.fDither = static_cast(dither); } /** \enum SkPaint::Style Set Style to fill, stroke, or both fill and stroke geometry. The stroke and fill share all paint attributes; for instance, they are drawn with the same color. Use kStrokeAndFill_Style to avoid hitting the same pixels twice with a stroke draw and a fill draw. */ enum Style : uint8_t { kFill_Style, //!< set to fill geometry kStroke_Style, //!< set to stroke geometry kStrokeAndFill_Style, //!< sets to stroke and fill geometry }; /** May be used to verify that SkPaint::Style is a legal value. */ static constexpr int kStyleCount = kStrokeAndFill_Style + 1; /** Returns whether the geometry is filled, stroked, or filled and stroked. */ Style getStyle() const { return (Style)fBitfields.fStyle; } /** Sets whether the geometry is filled, stroked, or filled and stroked. Has no effect if style is not a legal SkPaint::Style value. example: https://fiddle.skia.org/c/@Paint_setStyle example: https://fiddle.skia.org/c/@Stroke_Width */ void setStyle(Style style); /** * Set paint's style to kStroke if true, or kFill if false. */ void setStroke(bool); /** Retrieves alpha and RGB, unpremultiplied, packed into 32 bits. Use helpers SkColorGetA(), SkColorGetR(), SkColorGetG(), and SkColorGetB() to extract a color component. @return unpremultiplied ARGB */ SkColor getColor() const { return fColor4f.toSkColor(); } /** Retrieves alpha and RGB, unpremultiplied, as four floating point values. RGB are extended sRGB values (sRGB gamut, and encoded with the sRGB transfer function). @return unpremultiplied RGBA */ SkColor4f getColor4f() const { return fColor4f; } /** Sets alpha and RGB used when stroking and filling. The color is a 32-bit value, unpremultiplied, packing 8-bit components for alpha, red, blue, and green. @param color unpremultiplied ARGB example: https://fiddle.skia.org/c/@Paint_setColor */ void setColor(SkColor color); /** Sets alpha and RGB used when stroking and filling. The color is four floating point values, unpremultiplied. The color values are interpreted as being in the colorSpace. If colorSpace is nullptr, then color is assumed to be in the sRGB color space. @param color unpremultiplied RGBA @param colorSpace SkColorSpace describing the encoding of color */ void setColor(const SkColor4f& color, SkColorSpace* colorSpace = nullptr); void setColor4f(const SkColor4f& color, SkColorSpace* colorSpace = nullptr) { this->setColor(color, colorSpace); } /** Retrieves alpha from the color used when stroking and filling. @return alpha ranging from zero, fully transparent, to one, fully opaque */ float getAlphaf() const { return fColor4f.fA; } // Helper that scales the alpha by 255. uint8_t getAlpha() const { return static_cast(sk_float_round2int(this->getAlphaf() * 255)); } /** Replaces alpha, leaving RGB unchanged. An out of range value triggers an assert in the debug build. a is a value from 0.0 to 1.0. a set to zero makes color fully transparent; a set to 1.0 makes color fully opaque. @param a alpha component of color */ void setAlphaf(float a); // Helper that accepts an int between 0 and 255, and divides it by 255.0 void setAlpha(U8CPU a) { this->setAlphaf(a * (1.0f / 255)); } /** Sets color used when drawing solid fills. The color components range from 0 to 255. The color is unpremultiplied; alpha sets the transparency independent of RGB. @param a amount of alpha, from fully transparent (0) to fully opaque (255) @param r amount of red, from no red (0) to full red (255) @param g amount of green, from no green (0) to full green (255) @param b amount of blue, from no blue (0) to full blue (255) example: https://fiddle.skia.org/c/@Paint_setARGB */ void setARGB(U8CPU a, U8CPU r, U8CPU g, U8CPU b); /** Returns the thickness of the pen used by SkPaint to outline the shape. @return zero for hairline, greater than zero for pen thickness */ SkScalar getStrokeWidth() const { return fWidth; } /** Sets the thickness of the pen used by the paint to outline the shape. A stroke-width of zero is treated as "hairline" width. Hairlines are always exactly one pixel wide in device space (their thickness does not change as the canvas is scaled). Negative stroke-widths are invalid; setting a negative width will have no effect. @param width zero thickness for hairline; greater than zero for pen thickness example: https://fiddle.skia.org/c/@Miter_Limit example: https://fiddle.skia.org/c/@Paint_setStrokeWidth */ void setStrokeWidth(SkScalar width); /** Returns the limit at which a sharp corner is drawn beveled. @return zero and greater miter limit */ SkScalar getStrokeMiter() const { return fMiterLimit; } /** Sets the limit at which a sharp corner is drawn beveled. Valid values are zero and greater. Has no effect if miter is less than zero. @param miter zero and greater miter limit example: https://fiddle.skia.org/c/@Paint_setStrokeMiter */ void setStrokeMiter(SkScalar miter); /** \enum SkPaint::Cap Cap draws at the beginning and end of an open path contour. */ enum Cap { kButt_Cap, //!< no stroke extension kRound_Cap, //!< adds circle kSquare_Cap, //!< adds square kLast_Cap = kSquare_Cap, //!< largest Cap value kDefault_Cap = kButt_Cap, //!< equivalent to kButt_Cap }; /** May be used to verify that SkPaint::Cap is a legal value. */ static constexpr int kCapCount = kLast_Cap + 1; /** \enum SkPaint::Join Join specifies how corners are drawn when a shape is stroked. Join affects the four corners of a stroked rectangle, and the connected segments in a stroked path. Choose miter join to draw sharp corners. Choose round join to draw a circle with a radius equal to the stroke width on top of the corner. Choose bevel join to minimally connect the thick strokes. The fill path constructed to describe the stroked path respects the join setting but may not contain the actual join. For instance, a fill path constructed with round joins does not necessarily include circles at each connected segment. */ enum Join : uint8_t { kMiter_Join, //!< extends to miter limit kRound_Join, //!< adds circle kBevel_Join, //!< connects outside edges kLast_Join = kBevel_Join, //!< equivalent to the largest value for Join kDefault_Join = kMiter_Join, //!< equivalent to kMiter_Join }; /** May be used to verify that SkPaint::Join is a legal value. */ static constexpr int kJoinCount = kLast_Join + 1; /** Returns the geometry drawn at the beginning and end of strokes. */ Cap getStrokeCap() const { return (Cap)fBitfields.fCapType; } /** Sets the geometry drawn at the beginning and end of strokes. example: https://fiddle.skia.org/c/@Paint_setStrokeCap_a example: https://fiddle.skia.org/c/@Paint_setStrokeCap_b */ void setStrokeCap(Cap cap); /** Returns the geometry drawn at the corners of strokes. */ Join getStrokeJoin() const { return (Join)fBitfields.fJoinType; } /** Sets the geometry drawn at the corners of strokes. example: https://fiddle.skia.org/c/@Paint_setStrokeJoin */ void setStrokeJoin(Join join); /** Returns optional colors used when filling a path, such as a gradient. Does not alter SkShader SkRefCnt. @return SkShader if previously set, nullptr otherwise */ SkShader* getShader() const { return fShader.get(); } /** Returns optional colors used when filling a path, such as a gradient. Increases SkShader SkRefCnt by one. @return SkShader if previously set, nullptr otherwise example: https://fiddle.skia.org/c/@Paint_refShader */ sk_sp refShader() const; /** Sets optional colors used when filling a path, such as a gradient. Sets SkShader to shader, decreasing SkRefCnt of the previous SkShader. Increments shader SkRefCnt by one. @param shader how geometry is filled with color; if nullptr, color is used instead example: https://fiddle.skia.org/c/@Color_Filter_Methods example: https://fiddle.skia.org/c/@Paint_setShader */ void setShader(sk_sp shader); /** Returns SkColorFilter if set, or nullptr. Does not alter SkColorFilter SkRefCnt. @return SkColorFilter if previously set, nullptr otherwise */ SkColorFilter* getColorFilter() const { return fColorFilter.get(); } /** Returns SkColorFilter if set, or nullptr. Increases SkColorFilter SkRefCnt by one. @return SkColorFilter if set, or nullptr example: https://fiddle.skia.org/c/@Paint_refColorFilter */ sk_sp refColorFilter() const; /** Sets SkColorFilter to filter, decreasing SkRefCnt of the previous SkColorFilter. Pass nullptr to clear SkColorFilter. Increments filter SkRefCnt by one. @param colorFilter SkColorFilter to apply to subsequent draw example: https://fiddle.skia.org/c/@Blend_Mode_Methods example: https://fiddle.skia.org/c/@Paint_setColorFilter */ void setColorFilter(sk_sp colorFilter); /** If the current blender can be represented as a SkBlendMode enum, this returns that * enum in the optional's value(). If it cannot, then the returned optional does not * contain a value. */ std::optional asBlendMode() const; /** * Queries the blender, and if it can be represented as a SkBlendMode, return that mode, * else return the defaultMode provided. */ SkBlendMode getBlendMode_or(SkBlendMode defaultMode) const; /** Returns true iff the current blender claims to be equivalent to SkBlendMode::kSrcOver. * * Also returns true of the current blender is nullptr. */ bool isSrcOver() const; /** Helper method for calling setBlender(). * * This sets a blender that implements the specified blendmode enum. */ void setBlendMode(SkBlendMode mode); /** Returns the user-supplied blend function, if one has been set. * Does not alter SkBlender's SkRefCnt. * * A nullptr blender signifies the default SrcOver behavior. * * @return the SkBlender assigned to this paint, otherwise nullptr */ SkBlender* getBlender() const { return fBlender.get(); } /** Returns the user-supplied blend function, if one has been set. * Increments the SkBlender's SkRefCnt by one. * * A nullptr blender signifies the default SrcOver behavior. * * @return the SkBlender assigned to this paint, otherwise nullptr */ sk_sp refBlender() const; /** Sets the current blender, increasing its refcnt, and if a blender is already * present, decreasing that object's refcnt. * * A nullptr blender signifies the default SrcOver behavior. * * For convenience, you can call setBlendMode() if the blend effect can be expressed * as one of those values. */ void setBlender(sk_sp blender); /** Returns SkPathEffect if set, or nullptr. Does not alter SkPathEffect SkRefCnt. @return SkPathEffect if previously set, nullptr otherwise */ SkPathEffect* getPathEffect() const { return fPathEffect.get(); } /** Returns SkPathEffect if set, or nullptr. Increases SkPathEffect SkRefCnt by one. @return SkPathEffect if previously set, nullptr otherwise example: https://fiddle.skia.org/c/@Paint_refPathEffect */ sk_sp refPathEffect() const; /** Sets SkPathEffect to pathEffect, decreasing SkRefCnt of the previous SkPathEffect. Pass nullptr to leave the path geometry unaltered. Increments pathEffect SkRefCnt by one. @param pathEffect replace SkPath with a modification when drawn example: https://fiddle.skia.org/c/@Mask_Filter_Methods example: https://fiddle.skia.org/c/@Paint_setPathEffect */ void setPathEffect(sk_sp pathEffect); /** Returns SkMaskFilter if set, or nullptr. Does not alter SkMaskFilter SkRefCnt. @return SkMaskFilter if previously set, nullptr otherwise */ SkMaskFilter* getMaskFilter() const { return fMaskFilter.get(); } /** Returns SkMaskFilter if set, or nullptr. Increases SkMaskFilter SkRefCnt by one. @return SkMaskFilter if previously set, nullptr otherwise example: https://fiddle.skia.org/c/@Paint_refMaskFilter */ sk_sp refMaskFilter() const; /** Sets SkMaskFilter to maskFilter, decreasing SkRefCnt of the previous SkMaskFilter. Pass nullptr to clear SkMaskFilter and leave SkMaskFilter effect on mask alpha unaltered. Increments maskFilter SkRefCnt by one. @param maskFilter modifies clipping mask generated from drawn geometry example: https://fiddle.skia.org/c/@Paint_setMaskFilter example: https://fiddle.skia.org/c/@Typeface_Methods */ void setMaskFilter(sk_sp maskFilter); /** Returns SkImageFilter if set, or nullptr. Does not alter SkImageFilter SkRefCnt. @return SkImageFilter if previously set, nullptr otherwise */ SkImageFilter* getImageFilter() const { return fImageFilter.get(); } /** Returns SkImageFilter if set, or nullptr. Increases SkImageFilter SkRefCnt by one. @return SkImageFilter if previously set, nullptr otherwise example: https://fiddle.skia.org/c/@Paint_refImageFilter */ sk_sp refImageFilter() const; /** Sets SkImageFilter to imageFilter, decreasing SkRefCnt of the previous SkImageFilter. Pass nullptr to clear SkImageFilter, and remove SkImageFilter effect on drawing. Increments imageFilter SkRefCnt by one. @param imageFilter how SkImage is sampled when transformed example: https://fiddle.skia.org/c/@Paint_setImageFilter */ void setImageFilter(sk_sp imageFilter); /** Returns true if SkPaint prevents all drawing; otherwise, the SkPaint may or may not allow drawing. Returns true if, for example, SkBlendMode combined with alpha computes a new alpha of zero. @return true if SkPaint prevents all drawing example: https://fiddle.skia.org/c/@Paint_nothingToDraw */ bool nothingToDraw() const; /** (to be made private) Returns true if SkPaint does not include elements requiring extensive computation to compute device bounds of drawn geometry. For instance, SkPaint with SkPathEffect always returns false. @return true if SkPaint allows for fast computation of bounds */ bool canComputeFastBounds() const; /** (to be made private) Only call this if canComputeFastBounds() returned true. This takes a raw rectangle (the raw bounds of a shape), and adjusts it for stylistic effects in the paint (e.g. stroking). If needed, it uses the storage parameter. It returns the adjusted bounds that can then be used for SkCanvas::quickReject tests. The returned SkRect will either be orig or storage, thus the caller should not rely on storage being set to the result, but should always use the returned value. It is legal for orig and storage to be the same SkRect. For example: if (!path.isInverseFillType() && paint.canComputeFastBounds()) { SkRect storage; if (canvas->quickReject(paint.computeFastBounds(path.getBounds(), &storage))) { return; // do not draw the path } } // draw the path @param orig geometry modified by SkPaint when drawn @param storage computed bounds of geometry; may not be nullptr @return fast computed bounds */ const SkRect& computeFastBounds(const SkRect& orig, SkRect* storage) const; /** (to be made private) @param orig geometry modified by SkPaint when drawn @param storage computed bounds of geometry @return fast computed bounds */ const SkRect& computeFastStrokeBounds(const SkRect& orig, SkRect* storage) const { return this->doComputeFastBounds(orig, storage, kStroke_Style); } /** (to be made private) Computes the bounds, overriding the SkPaint SkPaint::Style. This can be used to account for additional width required by stroking orig, without altering SkPaint::Style set to fill. @param orig geometry modified by SkPaint when drawn @param storage computed bounds of geometry @param style overrides SkPaint::Style @return fast computed bounds */ const SkRect& doComputeFastBounds(const SkRect& orig, SkRect* storage, Style style) const; using sk_is_trivially_relocatable = std::true_type; private: sk_sp fPathEffect; sk_sp fShader; sk_sp fMaskFilter; sk_sp fColorFilter; sk_sp fImageFilter; sk_sp fBlender; SkColor4f fColor4f; SkScalar fWidth; SkScalar fMiterLimit; union { struct { unsigned fAntiAlias : 1; unsigned fDither : 1; unsigned fCapType : 2; unsigned fJoinType : 2; unsigned fStyle : 2; unsigned fPadding : 24; // 24 == 32 -1-1-2-2-2 } fBitfields; uint32_t fBitfieldsUInt; }; static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); friend class SkPaintPriv; }; class SkBitmap; class SkCanvas; class SkMatrix; class SkSurfaceProps; /** * If a client wants to control the allocation of raster layers in a canvas, it should subclass * SkRasterHandleAllocator. This allocator performs two tasks: * 1. controls how the memory for the pixels is allocated * 2. associates a "handle" to a private object that can track the matrix/clip of the SkCanvas * * This example allocates a canvas, and defers to the allocator to create the base layer. * * std::unique_ptr canvas = SkRasterHandleAllocator::MakeCanvas( * SkImageInfo::Make(...), * std::make_unique(...), * nullptr); * * If you have already allocated the base layer (and its handle, release-proc etc.) then you * can pass those in using the last parameter to MakeCanvas(). * * Regardless of how the base layer is allocated, each time canvas->saveLayer() is called, * your allocator's allocHandle() will be called. */ class SK_API SkRasterHandleAllocator { public: virtual ~SkRasterHandleAllocator() = default; // The value that is returned to clients of the canvas that has this allocator installed. typedef void* Handle; struct Rec { // When the allocation goes out of scope, this proc is called to free everything associated // with it: the pixels, the "handle", etc. This is passed the pixel address and fReleaseCtx. void (*fReleaseProc)(void* pixels, void* ctx); void* fReleaseCtx; // context passed to fReleaseProc void* fPixels; // pixels for this allocation size_t fRowBytes; // rowbytes for these pixels Handle fHandle; // public handle returned by SkCanvas::accessTopRasterHandle() }; /** * Given a requested info, allocate the corresponding pixels/rowbytes, and whatever handle * is desired to give clients access to those pixels. The rec also contains a proc and context * which will be called when this allocation goes out of scope. * * e.g. * when canvas->saveLayer() is called, the allocator will be called to allocate the pixels * for the layer. When canvas->restore() is called, the fReleaseProc will be called. */ virtual bool allocHandle(const SkImageInfo&, Rec*) = 0; /** * Clients access the handle for a given layer by calling SkCanvas::accessTopRasterHandle(). * To allow the handle to reflect the current matrix/clip in the canvs, updateHandle() is * is called. The subclass is responsible to update the handle as it sees fit. */ virtual void updateHandle(Handle, const SkMatrix&, const SkIRect&) = 0; /** * This creates a canvas which will use the allocator to manage pixel allocations, including * all calls to saveLayer(). * * If rec is non-null, then it will be used as the base-layer of pixels/handle. * If rec is null, then the allocator will be called for the base-layer as well. */ static std::unique_ptr MakeCanvas(std::unique_ptr, const SkImageInfo&, const Rec* rec = nullptr, const SkSurfaceProps* props = nullptr); protected: SkRasterHandleAllocator() = default; SkRasterHandleAllocator(const SkRasterHandleAllocator&) = delete; SkRasterHandleAllocator& operator=(const SkRasterHandleAllocator&) = delete; private: friend class SkBitmapDevice; Handle allocBitmap(const SkImageInfo&, SkBitmap*); }; /* Some helper functions for C strings */ static inline bool SkStrStartsWith(const char string[], const char prefixStr[]) { SkASSERT(string); SkASSERT(prefixStr); return !strncmp(string, prefixStr, strlen(prefixStr)); } static inline bool SkStrStartsWith(const char string[], const char prefixChar) { SkASSERT(string); return (prefixChar == *string); } bool SkStrEndsWith(const char string[], const char suffixStr[]); bool SkStrEndsWith(const char string[], const char suffixChar); int SkStrStartsWithOneOf(const char string[], const char prefixes[]); static inline int SkStrFind(const char string[], const char substring[]) { const char *first = strstr(string, substring); if (nullptr == first) return -1; return SkToInt(first - &string[0]); } static inline int SkStrFindLastOf(const char string[], const char subchar) { const char* last = strrchr(string, subchar); if (nullptr == last) return -1; return SkToInt(last - &string[0]); } static inline bool SkStrContains(const char string[], const char substring[]) { SkASSERT(string); SkASSERT(substring); return (-1 != SkStrFind(string, substring)); } static inline bool SkStrContains(const char string[], const char subchar) { SkASSERT(string); char tmp[2]; tmp[0] = subchar; tmp[1] = '\0'; return (-1 != SkStrFind(string, tmp)); } /* * The SkStrAppend... methods will write into the provided buffer, assuming it is large enough. * Each method has an associated const (e.g. kSkStrAppendU32_MaxSize) which will be the largest * value needed for that method's buffer. * * char storage[kSkStrAppendU32_MaxSize]; * SkStrAppendU32(storage, value); * * Note : none of the SkStrAppend... methods write a terminating 0 to their buffers. Instead, * the methods return the ptr to the end of the written part of the buffer. This can be used * to compute the length, and/or know where to write a 0 if that is desired. * * char storage[kSkStrAppendU32_MaxSize + 1]; * char* stop = SkStrAppendU32(storage, value); * size_t len = stop - storage; * *stop = 0; // valid, since storage was 1 byte larger than the max. */ static constexpr int kSkStrAppendU32_MaxSize = 10; char* SkStrAppendU32(char buffer[], uint32_t); static constexpr int kSkStrAppendU64_MaxSize = 20; char* SkStrAppendU64(char buffer[], uint64_t, int minDigits); static constexpr int kSkStrAppendS32_MaxSize = kSkStrAppendU32_MaxSize + 1; char* SkStrAppendS32(char buffer[], int32_t); static constexpr int kSkStrAppendS64_MaxSize = kSkStrAppendU64_MaxSize + 1; char* SkStrAppendS64(char buffer[], int64_t, int minDigits); /** * Floats have at most 8 significant digits, so we limit our %g to that. * However, the total string could be 15 characters: -1.2345678e-005 * * In theory we should only expect up to 2 digits for the exponent, but on * some platforms we have seen 3 (as in the example above). */ static constexpr int kSkStrAppendScalar_MaxSize = 15; /** * Write the scalar in decimal format into buffer, and return a pointer to * the next char after the last one written. Note: a terminating 0 is not * written into buffer, which must be at least kSkStrAppendScalar_MaxSize. * Thus if the caller wants to add a 0 at the end, buffer must be at least * kSkStrAppendScalar_MaxSize + 1 bytes large. */ char* SkStrAppendScalar(char buffer[], SkScalar); /** \class SkString Light weight class for managing strings. Uses reference counting to make string assignments and copies very fast with no extra RAM cost. Assumes UTF8 encoding. */ class SK_API SkString { public: SkString(); explicit SkString(size_t len); explicit SkString(const char text[]); SkString(const char text[], size_t len); SkString(const SkString&); SkString(SkString&&); explicit SkString(const std::string&); explicit SkString(std::string_view); ~SkString(); bool isEmpty() const { return 0 == fRec->fLength; } size_t size() const { return (size_t) fRec->fLength; } const char* data() const { return fRec->data(); } const char* c_str() const { return fRec->data(); } char operator[](size_t n) const { return this->c_str()[n]; } bool equals(const SkString&) const; bool equals(const char text[]) const; bool equals(const char text[], size_t len) const; bool startsWith(const char prefixStr[]) const { return SkStrStartsWith(fRec->data(), prefixStr); } bool startsWith(const char prefixChar) const { return SkStrStartsWith(fRec->data(), prefixChar); } bool endsWith(const char suffixStr[]) const { return SkStrEndsWith(fRec->data(), suffixStr); } bool endsWith(const char suffixChar) const { return SkStrEndsWith(fRec->data(), suffixChar); } bool contains(const char substring[]) const { return SkStrContains(fRec->data(), substring); } bool contains(const char subchar) const { return SkStrContains(fRec->data(), subchar); } int find(const char substring[]) const { return SkStrFind(fRec->data(), substring); } int findLastOf(const char subchar) const { return SkStrFindLastOf(fRec->data(), subchar); } friend bool operator==(const SkString& a, const SkString& b) { return a.equals(b); } friend bool operator!=(const SkString& a, const SkString& b) { return !a.equals(b); } // these methods edit the string SkString& operator=(const SkString&); SkString& operator=(SkString&&); SkString& operator=(const char text[]); char* data(); char& operator[](size_t n) { return this->data()[n]; } void reset(); /** String contents are preserved on resize. (For destructive resize, `set(nullptr, length)`.) * `resize` automatically reserves an extra byte at the end of the buffer for a null terminator. */ void resize(size_t len); void set(const SkString& src) { *this = src; } void set(const char text[]); void set(const char text[], size_t len); void set(std::string_view str) { this->set(str.data(), str.size()); } void insert(size_t offset, const char text[]); void insert(size_t offset, const char text[], size_t len); void insert(size_t offset, const SkString& str) { this->insert(offset, str.c_str(), str.size()); } void insert(size_t offset, std::string_view str) { this->insert(offset, str.data(), str.size()); } void insertUnichar(size_t offset, SkUnichar); void insertS32(size_t offset, int32_t value); void insertS64(size_t offset, int64_t value, int minDigits = 0); void insertU32(size_t offset, uint32_t value); void insertU64(size_t offset, uint64_t value, int minDigits = 0); void insertHex(size_t offset, uint32_t value, int minDigits = 0); void insertScalar(size_t offset, SkScalar); void append(const char text[]) { this->insert((size_t)-1, text); } void append(const char text[], size_t len) { this->insert((size_t)-1, text, len); } void append(const SkString& str) { this->insert((size_t)-1, str.c_str(), str.size()); } void append(std::string_view str) { this->insert((size_t)-1, str.data(), str.size()); } void appendUnichar(SkUnichar uni) { this->insertUnichar((size_t)-1, uni); } void appendS32(int32_t value) { this->insertS32((size_t)-1, value); } void appendS64(int64_t value, int minDigits = 0) { this->insertS64((size_t)-1, value, minDigits); } void appendU32(uint32_t value) { this->insertU32((size_t)-1, value); } void appendU64(uint64_t value, int minDigits = 0) { this->insertU64((size_t)-1, value, minDigits); } void appendHex(uint32_t value, int minDigits = 0) { this->insertHex((size_t)-1, value, minDigits); } void appendScalar(SkScalar value) { this->insertScalar((size_t)-1, value); } void prepend(const char text[]) { this->insert(0, text); } void prepend(const char text[], size_t len) { this->insert(0, text, len); } void prepend(const SkString& str) { this->insert(0, str.c_str(), str.size()); } void prepend(std::string_view str) { this->insert(0, str.data(), str.size()); } void prependUnichar(SkUnichar uni) { this->insertUnichar(0, uni); } void prependS32(int32_t value) { this->insertS32(0, value); } void prependS64(int32_t value, int minDigits = 0) { this->insertS64(0, value, minDigits); } void prependHex(uint32_t value, int minDigits = 0) { this->insertHex(0, value, minDigits); } void prependScalar(SkScalar value) { this->insertScalar((size_t)-1, value); } void printf(const char format[], ...) SK_PRINTF_LIKE(2, 3); void printVAList(const char format[], va_list) SK_PRINTF_LIKE(2, 0); void appendf(const char format[], ...) SK_PRINTF_LIKE(2, 3); void appendVAList(const char format[], va_list) SK_PRINTF_LIKE(2, 0); void prependf(const char format[], ...) SK_PRINTF_LIKE(2, 3); void prependVAList(const char format[], va_list) SK_PRINTF_LIKE(2, 0); void remove(size_t offset, size_t length); SkString& operator+=(const SkString& s) { this->append(s); return *this; } SkString& operator+=(const char text[]) { this->append(text); return *this; } SkString& operator+=(const char c) { this->append(&c, 1); return *this; } /** * Swap contents between this and other. This function is guaranteed * to never fail or throw. */ void swap(SkString& other); using sk_is_trivially_relocatable = std::true_type; private: struct Rec { public: constexpr Rec(uint32_t len, int32_t refCnt) : fLength(len), fRefCnt(refCnt) {} static sk_sp Make(const char text[], size_t len); char* data() { return fBeginningOfData; } const char* data() const { return fBeginningOfData; } void ref() const; void unref() const; bool unique() const; #ifdef SK_DEBUG int32_t getRefCnt() const; #endif uint32_t fLength; // logically size_t, but we want it to stay 32 bits private: mutable std::atomic fRefCnt; char fBeginningOfData[1] = {'\0'}; // Ensure the unsized delete is called. void operator delete(void* p) { ::operator delete(p); } }; sk_sp fRec; static_assert(::sk_is_trivially_relocatable::value); #ifdef SK_DEBUG SkString& validate(); const SkString& validate() const; #else SkString& validate() { return *this; } const SkString& validate() const { return *this; } #endif static const Rec gEmptyRec; }; /// Creates a new string and writes into it using a printf()-style format. SkString SkStringPrintf(const char* format, ...) SK_PRINTF_LIKE(1, 2); /// This makes it easier to write a caller as a VAR_ARGS function where the format string is /// optional. static inline SkString SkStringPrintf() { return SkString(); } static inline void swap(SkString& a, SkString& b) { a.swap(b); } /** * Description of how the LCD strips are arranged for each pixel. If this is unknown, or the * pixels are meant to be "portable" and/or transformed before showing (e.g. rotated, scaled) * then use kUnknown_SkPixelGeometry. */ enum SkPixelGeometry { kUnknown_SkPixelGeometry, kRGB_H_SkPixelGeometry, kBGR_H_SkPixelGeometry, kRGB_V_SkPixelGeometry, kBGR_V_SkPixelGeometry, }; // Returns true iff geo is a known geometry and is RGB. static inline bool SkPixelGeometryIsRGB(SkPixelGeometry geo) { return kRGB_H_SkPixelGeometry == geo || kRGB_V_SkPixelGeometry == geo; } // Returns true iff geo is a known geometry and is BGR. static inline bool SkPixelGeometryIsBGR(SkPixelGeometry geo) { return kBGR_H_SkPixelGeometry == geo || kBGR_V_SkPixelGeometry == geo; } // Returns true iff geo is a known geometry and is horizontal. static inline bool SkPixelGeometryIsH(SkPixelGeometry geo) { return kRGB_H_SkPixelGeometry == geo || kBGR_H_SkPixelGeometry == geo; } // Returns true iff geo is a known geometry and is vertical. static inline bool SkPixelGeometryIsV(SkPixelGeometry geo) { return kRGB_V_SkPixelGeometry == geo || kBGR_V_SkPixelGeometry == geo; } /** * Describes properties and constraints of a given SkSurface. The rendering engine can parse these * during drawing, and can sometimes optimize its performance (e.g. disabling an expensive * feature). */ class SK_API SkSurfaceProps { public: enum Flags { kUseDeviceIndependentFonts_Flag = 1 << 0, // Use internal MSAA to render to non-MSAA GPU surfaces. kDynamicMSAA_Flag = 1 << 1, // If set, all rendering will have dithering enabled // Currently this only impacts GPU backends kAlwaysDither_Flag = 1 << 2, }; /** Deprecated alias used by Chromium. Will be removed. */ static const Flags kUseDistanceFieldFonts_Flag = kUseDeviceIndependentFonts_Flag; /** No flags, unknown pixel geometry. */ SkSurfaceProps(); SkSurfaceProps(uint32_t flags, SkPixelGeometry); SkSurfaceProps(const SkSurfaceProps&) = default; SkSurfaceProps& operator=(const SkSurfaceProps&) = default; SkSurfaceProps cloneWithPixelGeometry(SkPixelGeometry newPixelGeometry) const { return SkSurfaceProps(fFlags, newPixelGeometry); } uint32_t flags() const { return fFlags; } SkPixelGeometry pixelGeometry() const { return fPixelGeometry; } bool isUseDeviceIndependentFonts() const { return SkToBool(fFlags & kUseDeviceIndependentFonts_Flag); } bool isAlwaysDither() const { return SkToBool(fFlags & kAlwaysDither_Flag); } bool operator==(const SkSurfaceProps& that) const { return fFlags == that.fFlags && fPixelGeometry == that.fPixelGeometry; } bool operator!=(const SkSurfaceProps& that) const { return !(*this == that); } private: uint32_t fFlags; SkPixelGeometry fPixelGeometry; }; /* * The deque class works by blindly creating memory space of a specified element * size. It manages the memory as a doubly linked list of blocks each of which * can contain multiple elements. Pushes and pops add/remove blocks from the * beginning/end of the list as necessary while each block tracks the used * portion of its memory. * One behavior to be aware of is that the pops do not immediately remove an * empty block from the beginning/end of the list (Presumably so push/pop pairs * on the block boundaries don't cause thrashing). This can result in the first/ * last element not residing in the first/last block. */ class SK_API SkDeque { public: /** * elemSize specifies the size of each individual element in the deque * allocCount specifies how many elements are to be allocated as a block */ explicit SkDeque(size_t elemSize, int allocCount = 1); SkDeque(size_t elemSize, void* storage, size_t storageSize, int allocCount = 1); ~SkDeque(); bool empty() const { return 0 == fCount; } int count() const { return fCount; } size_t elemSize() const { return fElemSize; } const void* front() const { return fFront; } const void* back() const { return fBack; } void* front() { return (void*)((const SkDeque*)this)->front(); } void* back() { return (void*)((const SkDeque*)this)->back(); } /** * push_front and push_back return a pointer to the memory space * for the new element */ void* push_front(); void* push_back(); void pop_front(); void pop_back(); private: struct Block; public: class Iter { public: enum IterStart { kFront_IterStart, kBack_IterStart, }; /** * Creates an uninitialized iterator. Must be reset() */ Iter(); Iter(const SkDeque& d, IterStart startLoc); void* next(); void* prev(); void reset(const SkDeque& d, IterStart startLoc); private: SkDeque::Block* fCurBlock; char* fPos; size_t fElemSize; }; // Inherit privately from Iter to prevent access to reverse iteration class F2BIter : private Iter { public: F2BIter() {} /** * Wrap Iter's 2 parameter ctor to force initialization to the * beginning of the deque */ F2BIter(const SkDeque& d) : INHERITED(d, kFront_IterStart) {} using Iter::next; /** * Wrap Iter::reset to force initialization to the beginning of the * deque */ void reset(const SkDeque& d) { this->INHERITED::reset(d, kFront_IterStart); } private: using INHERITED = Iter; }; private: // allow unit test to call numBlocksAllocated friend class DequeUnitTestHelper; void* fFront; void* fBack; Block* fFrontBlock; Block* fBackBlock; size_t fElemSize; void* fInitialStorage; int fCount; // number of elements in the deque int fAllocCount; // number of elements to allocate per block Block* allocateBlock(int allocCount); void freeBlock(Block* block); /** * This returns the number of chunk blocks allocated by the deque. It * can be used to gauge the effectiveness of the selected allocCount. */ int numBlocksAllocated() const; SkDeque(const SkDeque&) = delete; SkDeque& operator=(const SkDeque&) = delete; }; #ifndef SK_SUPPORT_LEGACY_GETTOTALMATRIX #define SK_SUPPORT_LEGACY_GETTOTALMATRIX #endif namespace sktext { class GlyphRunBuilder; class GlyphRunList; } class AutoLayerForImageFilter; class GrRecordingContext; class SkBitmap; class SkBlender; class SkData; class SkDevice; class SkDrawable; class SkFont; class SkImage; class SkMesh; class SkPaintFilterCanvas; class SkPath; class SkPicture; class SkPixmap; class SkRRect; class SkRegion; class SkShader; class SkSpecialImage; class SkSurface; class SkSurface_Base; class SkTextBlob; class SkVertices; struct SkDrawShadowRec; struct SkRSXform; namespace skgpu::graphite { class Recorder; } namespace sktext::gpu { class Slug; } namespace SkRecords { class Draw; } /** \class SkCanvas SkCanvas provides an interface for drawing, and how the drawing is clipped and transformed. SkCanvas contains a stack of SkMatrix and clip values. SkCanvas and SkPaint together provide the state to draw into SkSurface or SkDevice. Each SkCanvas draw call transforms the geometry of the object by the concatenation of all SkMatrix values in the stack. The transformed geometry is clipped by the intersection of all of clip values in the stack. The SkCanvas draw calls use SkPaint to supply drawing state such as color, SkTypeface, text size, stroke width, SkShader and so on. To draw to a pixel-based destination, create raster surface or GPU surface. Request SkCanvas from SkSurface to obtain the interface to draw. SkCanvas generated by raster surface draws to memory visible to the CPU. SkCanvas generated by GPU surface uses Vulkan or OpenGL to draw to the GPU. To draw to a document, obtain SkCanvas from SVG canvas, document PDF, or SkPictureRecorder. SkDocument based SkCanvas and other SkCanvas subclasses reference SkDevice describing the destination. SkCanvas can be constructed to draw to SkBitmap without first creating raster surface. This approach may be deprecated in the future. */ class SK_API SkCanvas { public: /** Allocates raster SkCanvas that will draw directly into pixels. SkCanvas is returned if all parameters are valid. Valid parameters include: info dimensions are zero or positive; info contains SkColorType and SkAlphaType supported by raster surface; pixels is not nullptr; rowBytes is zero or large enough to contain info width pixels of SkColorType. Pass zero for rowBytes to compute rowBytes from info width and size of pixel. If rowBytes is greater than zero, it must be equal to or greater than info width times bytes required for SkColorType. Pixel buffer size should be info height times computed rowBytes. Pixels are not initialized. To access pixels after drawing, call flush() or peekPixels(). @param info width, height, SkColorType, SkAlphaType, SkColorSpace, of raster surface; width, or height, or both, may be zero @param pixels pointer to destination pixels buffer @param rowBytes interval from one SkSurface row to the next, or zero @param props LCD striping orientation and setting for device independent fonts; may be nullptr @return SkCanvas if all parameters are valid; otherwise, nullptr */ static std::unique_ptr MakeRasterDirect(const SkImageInfo& info, void* pixels, size_t rowBytes, const SkSurfaceProps* props = nullptr); /** Allocates raster SkCanvas specified by inline image specification. Subsequent SkCanvas calls draw into pixels. SkColorType is set to kN32_SkColorType. SkAlphaType is set to kPremul_SkAlphaType. To access pixels after drawing, call flush() or peekPixels(). SkCanvas is returned if all parameters are valid. Valid parameters include: width and height are zero or positive; pixels is not nullptr; rowBytes is zero or large enough to contain width pixels of kN32_SkColorType. Pass zero for rowBytes to compute rowBytes from width and size of pixel. If rowBytes is greater than zero, it must be equal to or greater than width times bytes required for SkColorType. Pixel buffer size should be height times rowBytes. @param width pixel column count on raster surface created; must be zero or greater @param height pixel row count on raster surface created; must be zero or greater @param pixels pointer to destination pixels buffer; buffer size should be height times rowBytes @param rowBytes interval from one SkSurface row to the next, or zero @return SkCanvas if all parameters are valid; otherwise, nullptr */ static std::unique_ptr MakeRasterDirectN32(int width, int height, SkPMColor* pixels, size_t rowBytes) { return MakeRasterDirect(SkImageInfo::MakeN32Premul(width, height), pixels, rowBytes); } /** Creates an empty SkCanvas with no backing device or pixels, with a width and height of zero. @return empty SkCanvas example: https://fiddle.skia.org/c/@Canvas_empty_constructor */ SkCanvas(); /** Creates SkCanvas of the specified dimensions without a SkSurface. Used by subclasses with custom implementations for draw member functions. If props equals nullptr, SkSurfaceProps are created with SkSurfaceProps::InitType settings, which choose the pixel striping direction and order. Since a platform may dynamically change its direction when the device is rotated, and since a platform may have multiple monitors with different characteristics, it is best not to rely on this legacy behavior. @param width zero or greater @param height zero or greater @param props LCD striping orientation and setting for device independent fonts; may be nullptr @return SkCanvas placeholder with dimensions example: https://fiddle.skia.org/c/@Canvas_int_int_const_SkSurfaceProps_star */ SkCanvas(int width, int height, const SkSurfaceProps* props = nullptr); /** Private. For internal use only. */ explicit SkCanvas(sk_sp device); /** Constructs a canvas that draws into bitmap. Sets kUnknown_SkPixelGeometry in constructed SkSurface. SkBitmap is copied so that subsequently editing bitmap will not affect constructed SkCanvas. May be deprecated in the future. @param bitmap width, height, SkColorType, SkAlphaType, and pixel storage of raster surface @return SkCanvas that can be used to draw into bitmap example: https://fiddle.skia.org/c/@Canvas_copy_const_SkBitmap */ explicit SkCanvas(const SkBitmap& bitmap); #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK /** Private. */ enum class ColorBehavior { kLegacy, //!< placeholder }; /** Private. For use by Android framework only. @param bitmap specifies a bitmap for the canvas to draw into @param behavior specializes this constructor; value is unused @return SkCanvas that can be used to draw into bitmap */ SkCanvas(const SkBitmap& bitmap, ColorBehavior behavior); #endif /** Constructs a canvas that draws into bitmap. Use props to match the device characteristics, like LCD striping. bitmap is copied so that subsequently editing bitmap will not affect constructed SkCanvas. @param bitmap width, height, SkColorType, SkAlphaType, and pixel storage of raster surface @param props order and orientation of RGB striping; and whether to use device independent fonts @return SkCanvas that can be used to draw into bitmap example: https://fiddle.skia.org/c/@Canvas_const_SkBitmap_const_SkSurfaceProps */ SkCanvas(const SkBitmap& bitmap, const SkSurfaceProps& props); /** Draws saved layers, if any. Frees up resources used by SkCanvas. example: https://fiddle.skia.org/c/@Canvas_destructor */ virtual ~SkCanvas(); /** Returns SkImageInfo for SkCanvas. If SkCanvas is not associated with raster surface or GPU surface, returned SkColorType is set to kUnknown_SkColorType. @return dimensions and SkColorType of SkCanvas example: https://fiddle.skia.org/c/@Canvas_imageInfo */ SkImageInfo imageInfo() const; /** Copies SkSurfaceProps, if SkCanvas is associated with raster surface or GPU surface, and returns true. Otherwise, returns false and leave props unchanged. @param props storage for writable SkSurfaceProps @return true if SkSurfaceProps was copied DEPRECATED: Replace usage with getBaseProps() or getTopProps() example: https://fiddle.skia.org/c/@Canvas_getProps */ bool getProps(SkSurfaceProps* props) const; /** Returns the SkSurfaceProps associated with the canvas (i.e., at the base of the layer stack). @return base SkSurfaceProps */ SkSurfaceProps getBaseProps() const; /** Returns the SkSurfaceProps associated with the canvas that are currently active (i.e., at the top of the layer stack). This can differ from getBaseProps depending on the flags passed to saveLayer (see SaveLayerFlagsSet). @return SkSurfaceProps active in the current/top layer */ SkSurfaceProps getTopProps() const; /** Gets the size of the base or root layer in global canvas coordinates. The origin of the base layer is always (0,0). The area available for drawing may be smaller (due to clipping or saveLayer). @return integral width and height of base layer example: https://fiddle.skia.org/c/@Canvas_getBaseLayerSize */ virtual SkISize getBaseLayerSize() const; /** Creates SkSurface matching info and props, and associates it with SkCanvas. Returns nullptr if no match found. If props is nullptr, matches SkSurfaceProps in SkCanvas. If props is nullptr and SkCanvas does not have SkSurfaceProps, creates SkSurface with default SkSurfaceProps. @param info width, height, SkColorType, SkAlphaType, and SkColorSpace @param props SkSurfaceProps to match; may be nullptr to match SkCanvas @return SkSurface matching info and props, or nullptr if no match is available example: https://fiddle.skia.org/c/@Canvas_makeSurface */ sk_sp makeSurface(const SkImageInfo& info, const SkSurfaceProps* props = nullptr); /** Returns Ganesh context of the GPU surface associated with SkCanvas. @return GPU context, if available; nullptr otherwise example: https://fiddle.skia.org/c/@Canvas_recordingContext */ virtual GrRecordingContext* recordingContext() const; /** Returns Recorder for the GPU surface associated with SkCanvas. @return Recorder, if available; nullptr otherwise */ virtual skgpu::graphite::Recorder* recorder() const; /** Sometimes a canvas is owned by a surface. If it is, getSurface() will return a bare * pointer to that surface, else this will return nullptr. */ SkSurface* getSurface() const; /** Returns the pixel base address, SkImageInfo, rowBytes, and origin if the pixels can be read directly. The returned address is only valid while SkCanvas is in scope and unchanged. Any SkCanvas call or SkSurface call may invalidate the returned address and other returned values. If pixels are inaccessible, info, rowBytes, and origin are unchanged. @param info storage for writable pixels' SkImageInfo; may be nullptr @param rowBytes storage for writable pixels' row bytes; may be nullptr @param origin storage for SkCanvas top layer origin, its top-left corner; may be nullptr @return address of pixels, or nullptr if inaccessible example: https://fiddle.skia.org/c/@Canvas_accessTopLayerPixels_a example: https://fiddle.skia.org/c/@Canvas_accessTopLayerPixels_b */ void* accessTopLayerPixels(SkImageInfo* info, size_t* rowBytes, SkIPoint* origin = nullptr); /** Returns custom context that tracks the SkMatrix and clip. Use SkRasterHandleAllocator to blend Skia drawing with custom drawing, typically performed by the host platform user interface. The custom context returned is generated by SkRasterHandleAllocator::MakeCanvas, which creates a custom canvas with raster storage for the drawing destination. @return context of custom allocation example: https://fiddle.skia.org/c/@Canvas_accessTopRasterHandle */ SkRasterHandleAllocator::Handle accessTopRasterHandle() const; /** Returns true if SkCanvas has direct access to its pixels. Pixels are readable when SkDevice is raster. Pixels are not readable when SkCanvas is returned from GPU surface, returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class like DebugCanvas. pixmap is valid only while SkCanvas is in scope and unchanged. Any SkCanvas or SkSurface call may invalidate the pixmap values. @param pixmap storage for pixel state if pixels are readable; otherwise, ignored @return true if SkCanvas has direct access to pixels example: https://fiddle.skia.org/c/@Canvas_peekPixels */ bool peekPixels(SkPixmap* pixmap); /** Copies SkRect of pixels from SkCanvas into dstPixels. SkMatrix and clip are ignored. Source SkRect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()). Destination SkRect corners are (0, 0) and (dstInfo.width(), dstInfo.height()). Copies each readable pixel intersecting both rectangles, without scaling, converting to dstInfo.colorType() and dstInfo.alphaType() if required. Pixels are readable when SkDevice is raster, or backed by a GPU. Pixels are not readable when SkCanvas is returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class like DebugCanvas. The destination pixel storage must be allocated by the caller. Pixel values are converted only if SkColorType and SkAlphaType do not match. Only pixels within both source and destination rectangles are copied. dstPixels contents outside SkRect intersection are unchanged. Pass negative values for srcX or srcY to offset pixels across or down destination. Does not copy, and returns false if: - Source and destination rectangles do not intersect. - SkCanvas pixels could not be converted to dstInfo.colorType() or dstInfo.alphaType(). - SkCanvas pixels are not readable; for instance, SkCanvas is document-based. - dstRowBytes is too small to contain one row of pixels. @param dstInfo width, height, SkColorType, and SkAlphaType of dstPixels @param dstPixels storage for pixels; dstInfo.height() times dstRowBytes, or larger @param dstRowBytes size of one destination row; dstInfo.width() times pixel size, or larger @param srcX offset into readable pixels on x-axis; may be negative @param srcY offset into readable pixels on y-axis; may be negative @return true if pixels were copied */ bool readPixels(const SkImageInfo& dstInfo, void* dstPixels, size_t dstRowBytes, int srcX, int srcY); /** Copies SkRect of pixels from SkCanvas into pixmap. SkMatrix and clip are ignored. Source SkRect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()). Destination SkRect corners are (0, 0) and (pixmap.width(), pixmap.height()). Copies each readable pixel intersecting both rectangles, without scaling, converting to pixmap.colorType() and pixmap.alphaType() if required. Pixels are readable when SkDevice is raster, or backed by a GPU. Pixels are not readable when SkCanvas is returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class like DebugCanvas. Caller must allocate pixel storage in pixmap if needed. Pixel values are converted only if SkColorType and SkAlphaType do not match. Only pixels within both source and destination SkRect are copied. pixmap pixels contents outside SkRect intersection are unchanged. Pass negative values for srcX or srcY to offset pixels across or down pixmap. Does not copy, and returns false if: - Source and destination rectangles do not intersect. - SkCanvas pixels could not be converted to pixmap.colorType() or pixmap.alphaType(). - SkCanvas pixels are not readable; for instance, SkCanvas is document-based. - SkPixmap pixels could not be allocated. - pixmap.rowBytes() is too small to contain one row of pixels. @param pixmap storage for pixels copied from SkCanvas @param srcX offset into readable pixels on x-axis; may be negative @param srcY offset into readable pixels on y-axis; may be negative @return true if pixels were copied example: https://fiddle.skia.org/c/@Canvas_readPixels_2 */ bool readPixels(const SkPixmap& pixmap, int srcX, int srcY); /** Copies SkRect of pixels from SkCanvas into bitmap. SkMatrix and clip are ignored. Source SkRect corners are (srcX, srcY) and (imageInfo().width(), imageInfo().height()). Destination SkRect corners are (0, 0) and (bitmap.width(), bitmap.height()). Copies each readable pixel intersecting both rectangles, without scaling, converting to bitmap.colorType() and bitmap.alphaType() if required. Pixels are readable when SkDevice is raster, or backed by a GPU. Pixels are not readable when SkCanvas is returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class like DebugCanvas. Caller must allocate pixel storage in bitmap if needed. SkBitmap values are converted only if SkColorType and SkAlphaType do not match. Only pixels within both source and destination rectangles are copied. SkBitmap pixels outside SkRect intersection are unchanged. Pass negative values for srcX or srcY to offset pixels across or down bitmap. Does not copy, and returns false if: - Source and destination rectangles do not intersect. - SkCanvas pixels could not be converted to bitmap.colorType() or bitmap.alphaType(). - SkCanvas pixels are not readable; for instance, SkCanvas is document-based. - bitmap pixels could not be allocated. - bitmap.rowBytes() is too small to contain one row of pixels. @param bitmap storage for pixels copied from SkCanvas @param srcX offset into readable pixels on x-axis; may be negative @param srcY offset into readable pixels on y-axis; may be negative @return true if pixels were copied example: https://fiddle.skia.org/c/@Canvas_readPixels_3 */ bool readPixels(const SkBitmap& bitmap, int srcX, int srcY); /** Copies SkRect from pixels to SkCanvas. SkMatrix and clip are ignored. Source SkRect corners are (0, 0) and (info.width(), info.height()). Destination SkRect corners are (x, y) and (imageInfo().width(), imageInfo().height()). Copies each readable pixel intersecting both rectangles, without scaling, converting to imageInfo().colorType() and imageInfo().alphaType() if required. Pixels are writable when SkDevice is raster, or backed by a GPU. Pixels are not writable when SkCanvas is returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class like DebugCanvas. Pixel values are converted only if SkColorType and SkAlphaType do not match. Only pixels within both source and destination rectangles are copied. SkCanvas pixels outside SkRect intersection are unchanged. Pass negative values for x or y to offset pixels to the left or above SkCanvas pixels. Does not copy, and returns false if: - Source and destination rectangles do not intersect. - pixels could not be converted to SkCanvas imageInfo().colorType() or imageInfo().alphaType(). - SkCanvas pixels are not writable; for instance, SkCanvas is document-based. - rowBytes is too small to contain one row of pixels. @param info width, height, SkColorType, and SkAlphaType of pixels @param pixels pixels to copy, of size info.height() times rowBytes, or larger @param rowBytes size of one row of pixels; info.width() times pixel size, or larger @param x offset into SkCanvas writable pixels on x-axis; may be negative @param y offset into SkCanvas writable pixels on y-axis; may be negative @return true if pixels were written to SkCanvas example: https://fiddle.skia.org/c/@Canvas_writePixels */ bool writePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes, int x, int y); /** Copies SkRect from pixels to SkCanvas. SkMatrix and clip are ignored. Source SkRect corners are (0, 0) and (bitmap.width(), bitmap.height()). Destination SkRect corners are (x, y) and (imageInfo().width(), imageInfo().height()). Copies each readable pixel intersecting both rectangles, without scaling, converting to imageInfo().colorType() and imageInfo().alphaType() if required. Pixels are writable when SkDevice is raster, or backed by a GPU. Pixels are not writable when SkCanvas is returned by SkDocument::beginPage, returned by SkPictureRecorder::beginRecording, or SkCanvas is the base of a utility class like DebugCanvas. Pixel values are converted only if SkColorType and SkAlphaType do not match. Only pixels within both source and destination rectangles are copied. SkCanvas pixels outside SkRect intersection are unchanged. Pass negative values for x or y to offset pixels to the left or above SkCanvas pixels. Does not copy, and returns false if: - Source and destination rectangles do not intersect. - bitmap does not have allocated pixels. - bitmap pixels could not be converted to SkCanvas imageInfo().colorType() or imageInfo().alphaType(). - SkCanvas pixels are not writable; for instance, SkCanvas is document based. - bitmap pixels are inaccessible; for instance, bitmap wraps a texture. @param bitmap contains pixels copied to SkCanvas @param x offset into SkCanvas writable pixels on x-axis; may be negative @param y offset into SkCanvas writable pixels on y-axis; may be negative @return true if pixels were written to SkCanvas example: https://fiddle.skia.org/c/@Canvas_writePixels_2 example: https://fiddle.skia.org/c/@State_Stack_a example: https://fiddle.skia.org/c/@State_Stack_b */ bool writePixels(const SkBitmap& bitmap, int x, int y); /** Saves SkMatrix and clip. Calling restore() discards changes to SkMatrix and clip, restoring the SkMatrix and clip to their state when save() was called. SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), clipPath(), clipRegion(). Saved SkCanvas state is put on a stack; multiple calls to save() should be balance by an equal number of calls to restore(). Call restoreToCount() with result to restore this and subsequent saves. @return depth of saved stack example: https://fiddle.skia.org/c/@Canvas_save */ int save(); /** Saves SkMatrix and clip, and allocates a SkSurface for subsequent drawing. Calling restore() discards changes to SkMatrix and clip, and draws the SkSurface. SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), clipPath(), clipRegion(). SkRect bounds suggests but does not define the SkSurface size. To clip drawing to a specific rectangle, use clipRect(). Optional SkPaint paint applies alpha, SkColorFilter, SkImageFilter, and SkBlendMode when restore() is called. Call restoreToCount() with returned value to restore this and subsequent saves. @param bounds hint to limit the size of the layer; may be nullptr @param paint graphics state for layer; may be nullptr @return depth of saved stack example: https://fiddle.skia.org/c/@Canvas_saveLayer example: https://fiddle.skia.org/c/@Canvas_saveLayer_4 */ int saveLayer(const SkRect* bounds, const SkPaint* paint); /** Saves SkMatrix and clip, and allocates a SkSurface for subsequent drawing. Calling restore() discards changes to SkMatrix and clip, and draws the SkSurface. SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), clipPath(), clipRegion(). SkRect bounds suggests but does not define the layer size. To clip drawing to a specific rectangle, use clipRect(). Optional SkPaint paint applies alpha, SkColorFilter, SkImageFilter, and SkBlendMode when restore() is called. Call restoreToCount() with returned value to restore this and subsequent saves. @param bounds hint to limit the size of layer; may be nullptr @param paint graphics state for layer; may be nullptr @return depth of saved stack */ int saveLayer(const SkRect& bounds, const SkPaint* paint) { return this->saveLayer(&bounds, paint); } /** Saves SkMatrix and clip, and allocates SkSurface for subsequent drawing. Calling restore() discards changes to SkMatrix and clip, and blends layer with alpha opacity onto prior layer. SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), clipPath(), clipRegion(). SkRect bounds suggests but does not define layer size. To clip drawing to a specific rectangle, use clipRect(). alpha of zero is fully transparent, 1.0f is fully opaque. Call restoreToCount() with returned value to restore this and subsequent saves. @param bounds hint to limit the size of layer; may be nullptr @param alpha opacity of layer @return depth of saved stack example: https://fiddle.skia.org/c/@Canvas_saveLayerAlpha */ int saveLayerAlphaf(const SkRect* bounds, float alpha); // Helper that accepts an int between 0 and 255, and divides it by 255.0 int saveLayerAlpha(const SkRect* bounds, U8CPU alpha) { return this->saveLayerAlphaf(bounds, alpha * (1.0f / 255)); } /** \enum SkCanvas::SaveLayerFlagsSet SaveLayerFlags provides options that may be used in any combination in SaveLayerRec, defining how layer allocated by saveLayer() operates. It may be set to zero, kPreserveLCDText_SaveLayerFlag, kInitWithPrevious_SaveLayerFlag, or both flags. */ enum SaveLayerFlagsSet { kPreserveLCDText_SaveLayerFlag = 1 << 1, kInitWithPrevious_SaveLayerFlag = 1 << 2, //!< initializes with previous contents // instead of matching previous layer's colortype, use F16 kF16ColorType = 1 << 4, }; typedef uint32_t SaveLayerFlags; /** \struct SkCanvas::SaveLayerRec SaveLayerRec contains the state used to create the layer. */ struct SaveLayerRec { /** Sets fBounds, fPaint, and fBackdrop to nullptr. Clears fSaveLayerFlags. @return empty SaveLayerRec */ SaveLayerRec() {} /** Sets fBounds, fPaint, and fSaveLayerFlags; sets fBackdrop to nullptr. @param bounds layer dimensions; may be nullptr @param paint applied to layer when overlaying prior layer; may be nullptr @param saveLayerFlags SaveLayerRec options to modify layer @return SaveLayerRec with empty fBackdrop */ SaveLayerRec(const SkRect* bounds, const SkPaint* paint, SaveLayerFlags saveLayerFlags = 0) : SaveLayerRec(bounds, paint, nullptr, 1.f, saveLayerFlags) {} /** Sets fBounds, fPaint, fBackdrop, and fSaveLayerFlags. @param bounds layer dimensions; may be nullptr @param paint applied to layer when overlaying prior layer; may be nullptr @param backdrop If not null, this causes the current layer to be filtered by backdrop, and then drawn into the new layer (respecting the current clip). If null, the new layer is initialized with transparent-black. @param saveLayerFlags SaveLayerRec options to modify layer @return SaveLayerRec fully specified */ SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, SaveLayerFlags saveLayerFlags) : SaveLayerRec(bounds, paint, backdrop, 1.f, saveLayerFlags) {} /** hints at layer size limit */ const SkRect* fBounds = nullptr; /** modifies overlay */ const SkPaint* fPaint = nullptr; /** * If not null, this triggers the same initialization behavior as setting * kInitWithPrevious_SaveLayerFlag on fSaveLayerFlags: the current layer is copied into * the new layer, rather than initializing the new layer with transparent-black. * This is then filtered by fBackdrop (respecting the current clip). */ const SkImageFilter* fBackdrop = nullptr; /** preserves LCD text, creates with prior layer contents */ SaveLayerFlags fSaveLayerFlags = 0; private: friend class SkCanvas; friend class SkCanvasPriv; SaveLayerRec(const SkRect* bounds, const SkPaint* paint, const SkImageFilter* backdrop, SkScalar backdropScale, SaveLayerFlags saveLayerFlags) : fBounds(bounds) , fPaint(paint) , fBackdrop(backdrop) , fSaveLayerFlags(saveLayerFlags) , fExperimentalBackdropScale(backdropScale) {} // Relative scale factor that the image content used to initialize the layer when the // kInitFromPrevious flag or a backdrop filter is used. SkScalar fExperimentalBackdropScale = 1.f; }; /** Saves SkMatrix and clip, and allocates SkSurface for subsequent drawing. Calling restore() discards changes to SkMatrix and clip, and blends SkSurface with alpha opacity onto the prior layer. SkMatrix may be changed by translate(), scale(), rotate(), skew(), concat(), setMatrix(), and resetMatrix(). Clip may be changed by clipRect(), clipRRect(), clipPath(), clipRegion(). SaveLayerRec contains the state used to create the layer. Call restoreToCount() with returned value to restore this and subsequent saves. @param layerRec layer state @return depth of save state stack before this call was made. example: https://fiddle.skia.org/c/@Canvas_saveLayer_3 */ int saveLayer(const SaveLayerRec& layerRec); /** Removes changes to SkMatrix and clip since SkCanvas state was last saved. The state is removed from the stack. Does nothing if the stack is empty. example: https://fiddle.skia.org/c/@AutoCanvasRestore_restore example: https://fiddle.skia.org/c/@Canvas_restore */ void restore(); /** Returns the number of saved states, each containing: SkMatrix and clip. Equals the number of save() calls less the number of restore() calls plus one. The save count of a new canvas is one. @return depth of save state stack example: https://fiddle.skia.org/c/@Canvas_getSaveCount */ int getSaveCount() const; /** Restores state to SkMatrix and clip values when save(), saveLayer(), saveLayerPreserveLCDTextRequests(), or saveLayerAlpha() returned saveCount. Does nothing if saveCount is greater than state stack count. Restores state to initial values if saveCount is less than or equal to one. @param saveCount depth of state stack to restore example: https://fiddle.skia.org/c/@Canvas_restoreToCount */ void restoreToCount(int saveCount); /** Translates SkMatrix by dx along the x-axis and dy along the y-axis. Mathematically, replaces SkMatrix with a translation matrix premultiplied with SkMatrix. This has the effect of moving the drawing by (dx, dy) before transforming the result with SkMatrix. @param dx distance to translate on x-axis @param dy distance to translate on y-axis example: https://fiddle.skia.org/c/@Canvas_translate */ void translate(SkScalar dx, SkScalar dy); /** Scales SkMatrix by sx on the x-axis and sy on the y-axis. Mathematically, replaces SkMatrix with a scale matrix premultiplied with SkMatrix. This has the effect of scaling the drawing by (sx, sy) before transforming the result with SkMatrix. @param sx amount to scale on x-axis @param sy amount to scale on y-axis example: https://fiddle.skia.org/c/@Canvas_scale */ void scale(SkScalar sx, SkScalar sy); /** Rotates SkMatrix by degrees. Positive degrees rotates clockwise. Mathematically, replaces SkMatrix with a rotation matrix premultiplied with SkMatrix. This has the effect of rotating the drawing by degrees before transforming the result with SkMatrix. @param degrees amount to rotate, in degrees example: https://fiddle.skia.org/c/@Canvas_rotate */ void rotate(SkScalar degrees); /** Rotates SkMatrix by degrees about a point at (px, py). Positive degrees rotates clockwise. Mathematically, constructs a rotation matrix; premultiplies the rotation matrix by a translation matrix; then replaces SkMatrix with the resulting matrix premultiplied with SkMatrix. This has the effect of rotating the drawing about a given point before transforming the result with SkMatrix. @param degrees amount to rotate, in degrees @param px x-axis value of the point to rotate about @param py y-axis value of the point to rotate about example: https://fiddle.skia.org/c/@Canvas_rotate_2 */ void rotate(SkScalar degrees, SkScalar px, SkScalar py); /** Skews SkMatrix by sx on the x-axis and sy on the y-axis. A positive value of sx skews the drawing right as y-axis values increase; a positive value of sy skews the drawing down as x-axis values increase. Mathematically, replaces SkMatrix with a skew matrix premultiplied with SkMatrix. This has the effect of skewing the drawing by (sx, sy) before transforming the result with SkMatrix. @param sx amount to skew on x-axis @param sy amount to skew on y-axis example: https://fiddle.skia.org/c/@Canvas_skew */ void skew(SkScalar sx, SkScalar sy); /** Replaces SkMatrix with matrix premultiplied with existing SkMatrix. This has the effect of transforming the drawn geometry by matrix, before transforming the result with existing SkMatrix. @param matrix matrix to premultiply with existing SkMatrix example: https://fiddle.skia.org/c/@Canvas_concat */ void concat(const SkMatrix& matrix); void concat(const SkM44&); /** Replaces SkMatrix with matrix. Unlike concat(), any prior matrix state is overwritten. @param matrix matrix to copy, replacing existing SkMatrix example: https://fiddle.skia.org/c/@Canvas_setMatrix */ void setMatrix(const SkM44& matrix); // DEPRECATED -- use SkM44 version void setMatrix(const SkMatrix& matrix); /** Sets SkMatrix to the identity matrix. Any prior matrix state is overwritten. example: https://fiddle.skia.org/c/@Canvas_resetMatrix */ void resetMatrix(); /** Replaces clip with the intersection or difference of clip and rect, with an aliased or anti-aliased clip edge. rect is transformed by SkMatrix before it is combined with clip. @param rect SkRect to combine with clip @param op SkClipOp to apply to clip @param doAntiAlias true if clip is to be anti-aliased example: https://fiddle.skia.org/c/@Canvas_clipRect */ void clipRect(const SkRect& rect, SkClipOp op, bool doAntiAlias); /** Replaces clip with the intersection or difference of clip and rect. Resulting clip is aliased; pixels are fully contained by the clip. rect is transformed by SkMatrix before it is combined with clip. @param rect SkRect to combine with clip @param op SkClipOp to apply to clip */ void clipRect(const SkRect& rect, SkClipOp op) { this->clipRect(rect, op, false); } /** Replaces clip with the intersection of clip and rect. Resulting clip is aliased; pixels are fully contained by the clip. rect is transformed by SkMatrix before it is combined with clip. @param rect SkRect to combine with clip @param doAntiAlias true if clip is to be anti-aliased */ void clipRect(const SkRect& rect, bool doAntiAlias = false) { this->clipRect(rect, SkClipOp::kIntersect, doAntiAlias); } void clipIRect(const SkIRect& irect, SkClipOp op = SkClipOp::kIntersect) { this->clipRect(SkRect::Make(irect), op, false); } /** Sets the maximum clip rectangle, which can be set by clipRect(), clipRRect() and clipPath() and intersect the current clip with the specified rect. The maximum clip affects only future clipping operations; it is not retroactive. The clip restriction is not recorded in pictures. Pass an empty rect to disable maximum clip. This private API is for use by Android framework only. DEPRECATED: Replace usage with SkAndroidFrameworkUtils::replaceClip() @param rect maximum allowed clip in device coordinates */ void androidFramework_setDeviceClipRestriction(const SkIRect& rect); /** Replaces clip with the intersection or difference of clip and rrect, with an aliased or anti-aliased clip edge. rrect is transformed by SkMatrix before it is combined with clip. @param rrect SkRRect to combine with clip @param op SkClipOp to apply to clip @param doAntiAlias true if clip is to be anti-aliased example: https://fiddle.skia.org/c/@Canvas_clipRRect */ void clipRRect(const SkRRect& rrect, SkClipOp op, bool doAntiAlias); /** Replaces clip with the intersection or difference of clip and rrect. Resulting clip is aliased; pixels are fully contained by the clip. rrect is transformed by SkMatrix before it is combined with clip. @param rrect SkRRect to combine with clip @param op SkClipOp to apply to clip */ void clipRRect(const SkRRect& rrect, SkClipOp op) { this->clipRRect(rrect, op, false); } /** Replaces clip with the intersection of clip and rrect, with an aliased or anti-aliased clip edge. rrect is transformed by SkMatrix before it is combined with clip. @param rrect SkRRect to combine with clip @param doAntiAlias true if clip is to be anti-aliased */ void clipRRect(const SkRRect& rrect, bool doAntiAlias = false) { this->clipRRect(rrect, SkClipOp::kIntersect, doAntiAlias); } /** Replaces clip with the intersection or difference of clip and path, with an aliased or anti-aliased clip edge. SkPath::FillType determines if path describes the area inside or outside its contours; and if path contour overlaps itself or another path contour, whether the overlaps form part of the area. path is transformed by SkMatrix before it is combined with clip. @param path SkPath to combine with clip @param op SkClipOp to apply to clip @param doAntiAlias true if clip is to be anti-aliased example: https://fiddle.skia.org/c/@Canvas_clipPath */ void clipPath(const SkPath& path, SkClipOp op, bool doAntiAlias); /** Replaces clip with the intersection or difference of clip and path. Resulting clip is aliased; pixels are fully contained by the clip. SkPath::FillType determines if path describes the area inside or outside its contours; and if path contour overlaps itself or another path contour, whether the overlaps form part of the area. path is transformed by SkMatrix before it is combined with clip. @param path SkPath to combine with clip @param op SkClipOp to apply to clip */ void clipPath(const SkPath& path, SkClipOp op) { this->clipPath(path, op, false); } /** Replaces clip with the intersection of clip and path. Resulting clip is aliased; pixels are fully contained by the clip. SkPath::FillType determines if path describes the area inside or outside its contours; and if path contour overlaps itself or another path contour, whether the overlaps form part of the area. path is transformed by SkMatrix before it is combined with clip. @param path SkPath to combine with clip @param doAntiAlias true if clip is to be anti-aliased */ void clipPath(const SkPath& path, bool doAntiAlias = false) { this->clipPath(path, SkClipOp::kIntersect, doAntiAlias); } void clipShader(sk_sp, SkClipOp = SkClipOp::kIntersect); /** Replaces clip with the intersection or difference of clip and SkRegion deviceRgn. Resulting clip is aliased; pixels are fully contained by the clip. deviceRgn is unaffected by SkMatrix. @param deviceRgn SkRegion to combine with clip @param op SkClipOp to apply to clip example: https://fiddle.skia.org/c/@Canvas_clipRegion */ void clipRegion(const SkRegion& deviceRgn, SkClipOp op = SkClipOp::kIntersect); /** Returns true if SkRect rect, transformed by SkMatrix, can be quickly determined to be outside of clip. May return false even though rect is outside of clip. Use to check if an area to be drawn is clipped out, to skip subsequent draw calls. @param rect SkRect to compare with clip @return true if rect, transformed by SkMatrix, does not intersect clip example: https://fiddle.skia.org/c/@Canvas_quickReject */ bool quickReject(const SkRect& rect) const; /** Returns true if path, transformed by SkMatrix, can be quickly determined to be outside of clip. May return false even though path is outside of clip. Use to check if an area to be drawn is clipped out, to skip subsequent draw calls. @param path SkPath to compare with clip @return true if path, transformed by SkMatrix, does not intersect clip example: https://fiddle.skia.org/c/@Canvas_quickReject_2 */ bool quickReject(const SkPath& path) const; /** Returns bounds of clip, transformed by inverse of SkMatrix. If clip is empty, return SkRect::MakeEmpty, where all SkRect sides equal zero. SkRect returned is outset by one to account for partial pixel coverage if clip is anti-aliased. @return bounds of clip in local coordinates example: https://fiddle.skia.org/c/@Canvas_getLocalClipBounds */ SkRect getLocalClipBounds() const; /** Returns bounds of clip, transformed by inverse of SkMatrix. If clip is empty, return false, and set bounds to SkRect::MakeEmpty, where all SkRect sides equal zero. bounds is outset by one to account for partial pixel coverage if clip is anti-aliased. @param bounds SkRect of clip in local coordinates @return true if clip bounds is not empty */ bool getLocalClipBounds(SkRect* bounds) const { *bounds = this->getLocalClipBounds(); return !bounds->isEmpty(); } /** Returns SkIRect bounds of clip, unaffected by SkMatrix. If clip is empty, return SkRect::MakeEmpty, where all SkRect sides equal zero. Unlike getLocalClipBounds(), returned SkIRect is not outset. @return bounds of clip in base device coordinates example: https://fiddle.skia.org/c/@Canvas_getDeviceClipBounds */ SkIRect getDeviceClipBounds() const; /** Returns SkIRect bounds of clip, unaffected by SkMatrix. If clip is empty, return false, and set bounds to SkRect::MakeEmpty, where all SkRect sides equal zero. Unlike getLocalClipBounds(), bounds is not outset. @param bounds SkRect of clip in device coordinates @return true if clip bounds is not empty */ bool getDeviceClipBounds(SkIRect* bounds) const { *bounds = this->getDeviceClipBounds(); return !bounds->isEmpty(); } /** Fills clip with color color. mode determines how ARGB is combined with destination. @param color unpremultiplied ARGB @param mode SkBlendMode used to combine source color and destination example: https://fiddle.skia.org/c/@Canvas_drawColor */ void drawColor(SkColor color, SkBlendMode mode = SkBlendMode::kSrcOver) { this->drawColor(SkColor4f::FromColor(color), mode); } /** Fills clip with color color. mode determines how ARGB is combined with destination. @param color SkColor4f representing unpremultiplied color. @param mode SkBlendMode used to combine source color and destination */ void drawColor(const SkColor4f& color, SkBlendMode mode = SkBlendMode::kSrcOver); /** Fills clip with color color using SkBlendMode::kSrc. This has the effect of replacing all pixels contained by clip with color. @param color unpremultiplied ARGB */ void clear(SkColor color) { this->clear(SkColor4f::FromColor(color)); } /** Fills clip with color color using SkBlendMode::kSrc. This has the effect of replacing all pixels contained by clip with color. @param color SkColor4f representing unpremultiplied color. */ void clear(const SkColor4f& color) { this->drawColor(color, SkBlendMode::kSrc); } /** Makes SkCanvas contents undefined. Subsequent calls that read SkCanvas pixels, such as drawing with SkBlendMode, return undefined results. discard() does not change clip or SkMatrix. discard() may do nothing, depending on the implementation of SkSurface or SkDevice that created SkCanvas. discard() allows optimized performance on subsequent draws by removing cached data associated with SkSurface or SkDevice. It is not necessary to call discard() once done with SkCanvas; any cached data is deleted when owning SkSurface or SkDevice is deleted. */ void discard() { this->onDiscard(); } /** Fills clip with SkPaint paint. SkPaint components, SkShader, SkColorFilter, SkImageFilter, and SkBlendMode affect drawing; SkMaskFilter and SkPathEffect in paint are ignored. @param paint graphics state used to fill SkCanvas example: https://fiddle.skia.org/c/@Canvas_drawPaint */ void drawPaint(const SkPaint& paint); /** \enum SkCanvas::PointMode Selects if an array of points are drawn as discrete points, as lines, or as an open polygon. */ enum PointMode { kPoints_PointMode, //!< draw each point separately kLines_PointMode, //!< draw each pair of points as a line segment kPolygon_PointMode, //!< draw the array of points as a open polygon }; /** Draws pts using clip, SkMatrix and SkPaint paint. count is the number of points; if count is less than one, has no effect. mode may be one of: kPoints_PointMode, kLines_PointMode, or kPolygon_PointMode. If mode is kPoints_PointMode, the shape of point drawn depends on paint SkPaint::Cap. If paint is set to SkPaint::kRound_Cap, each point draws a circle of diameter SkPaint stroke width. If paint is set to SkPaint::kSquare_Cap or SkPaint::kButt_Cap, each point draws a square of width and height SkPaint stroke width. If mode is kLines_PointMode, each pair of points draws a line segment. One line is drawn for every two points; each point is used once. If count is odd, the final point is ignored. If mode is kPolygon_PointMode, each adjacent pair of points draws a line segment. count minus one lines are drawn; the first and last point are used once. Each line segment respects paint SkPaint::Cap and SkPaint stroke width. SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. Always draws each element one at a time; is not affected by SkPaint::Join, and unlike drawPath(), does not create a mask from all points and lines before drawing. @param mode whether pts draws points or lines @param count number of points in the array @param pts array of points to draw @param paint stroke, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawPoints */ void drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint); /** Draws point at (x, y) using clip, SkMatrix and SkPaint paint. The shape of point drawn depends on paint SkPaint::Cap. If paint is set to SkPaint::kRound_Cap, draw a circle of diameter SkPaint stroke width. If paint is set to SkPaint::kSquare_Cap or SkPaint::kButt_Cap, draw a square of width and height SkPaint stroke width. SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. @param x left edge of circle or square @param y top edge of circle or square @param paint stroke, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawPoint */ void drawPoint(SkScalar x, SkScalar y, const SkPaint& paint); /** Draws point p using clip, SkMatrix and SkPaint paint. The shape of point drawn depends on paint SkPaint::Cap. If paint is set to SkPaint::kRound_Cap, draw a circle of diameter SkPaint stroke width. If paint is set to SkPaint::kSquare_Cap or SkPaint::kButt_Cap, draw a square of width and height SkPaint stroke width. SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. @param p top-left edge of circle or square @param paint stroke, blend, color, and so on, used to draw */ void drawPoint(SkPoint p, const SkPaint& paint) { this->drawPoint(p.x(), p.y(), paint); } /** Draws line segment from (x0, y0) to (x1, y1) using clip, SkMatrix, and SkPaint paint. In paint: SkPaint stroke width describes the line thickness; SkPaint::Cap draws the end rounded or square; SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. @param x0 start of line segment on x-axis @param y0 start of line segment on y-axis @param x1 end of line segment on x-axis @param y1 end of line segment on y-axis @param paint stroke, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawLine */ void drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint); /** Draws line segment from p0 to p1 using clip, SkMatrix, and SkPaint paint. In paint: SkPaint stroke width describes the line thickness; SkPaint::Cap draws the end rounded or square; SkPaint::Style is ignored, as if were set to SkPaint::kStroke_Style. @param p0 start of line segment @param p1 end of line segment @param paint stroke, blend, color, and so on, used to draw */ void drawLine(SkPoint p0, SkPoint p1, const SkPaint& paint) { this->drawLine(p0.x(), p0.y(), p1.x(), p1.y(), paint); } /** Draws SkRect rect using clip, SkMatrix, and SkPaint paint. In paint: SkPaint::Style determines if rectangle is stroked or filled; if stroked, SkPaint stroke width describes the line thickness, and SkPaint::Join draws the corners rounded or square. @param rect rectangle to draw @param paint stroke or fill, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawRect */ void drawRect(const SkRect& rect, const SkPaint& paint); /** Draws SkIRect rect using clip, SkMatrix, and SkPaint paint. In paint: SkPaint::Style determines if rectangle is stroked or filled; if stroked, SkPaint stroke width describes the line thickness, and SkPaint::Join draws the corners rounded or square. @param rect rectangle to draw @param paint stroke or fill, blend, color, and so on, used to draw */ void drawIRect(const SkIRect& rect, const SkPaint& paint) { SkRect r; r.set(rect); // promotes the ints to scalars this->drawRect(r, paint); } /** Draws SkRegion region using clip, SkMatrix, and SkPaint paint. In paint: SkPaint::Style determines if rectangle is stroked or filled; if stroked, SkPaint stroke width describes the line thickness, and SkPaint::Join draws the corners rounded or square. @param region region to draw @param paint SkPaint stroke or fill, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawRegion */ void drawRegion(const SkRegion& region, const SkPaint& paint); /** Draws oval oval using clip, SkMatrix, and SkPaint. In paint: SkPaint::Style determines if oval is stroked or filled; if stroked, SkPaint stroke width describes the line thickness. @param oval SkRect bounds of oval @param paint SkPaint stroke or fill, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawOval */ void drawOval(const SkRect& oval, const SkPaint& paint); /** Draws SkRRect rrect using clip, SkMatrix, and SkPaint paint. In paint: SkPaint::Style determines if rrect is stroked or filled; if stroked, SkPaint stroke width describes the line thickness. rrect may represent a rectangle, circle, oval, uniformly rounded rectangle, or may have any combination of positive non-square radii for the four corners. @param rrect SkRRect with up to eight corner radii to draw @param paint SkPaint stroke or fill, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawRRect */ void drawRRect(const SkRRect& rrect, const SkPaint& paint); /** Draws SkRRect outer and inner using clip, SkMatrix, and SkPaint paint. outer must contain inner or the drawing is undefined. In paint: SkPaint::Style determines if SkRRect is stroked or filled; if stroked, SkPaint stroke width describes the line thickness. If stroked and SkRRect corner has zero length radii, SkPaint::Join can draw corners rounded or square. GPU-backed platforms optimize drawing when both outer and inner are concave and outer contains inner. These platforms may not be able to draw SkPath built with identical data as fast. @param outer SkRRect outer bounds to draw @param inner SkRRect inner bounds to draw @param paint SkPaint stroke or fill, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawDRRect_a example: https://fiddle.skia.org/c/@Canvas_drawDRRect_b */ void drawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint); /** Draws circle at (cx, cy) with radius using clip, SkMatrix, and SkPaint paint. If radius is zero or less, nothing is drawn. In paint: SkPaint::Style determines if circle is stroked or filled; if stroked, SkPaint stroke width describes the line thickness. @param cx circle center on the x-axis @param cy circle center on the y-axis @param radius half the diameter of circle @param paint SkPaint stroke or fill, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawCircle */ void drawCircle(SkScalar cx, SkScalar cy, SkScalar radius, const SkPaint& paint); /** Draws circle at center with radius using clip, SkMatrix, and SkPaint paint. If radius is zero or less, nothing is drawn. In paint: SkPaint::Style determines if circle is stroked or filled; if stroked, SkPaint stroke width describes the line thickness. @param center circle center @param radius half the diameter of circle @param paint SkPaint stroke or fill, blend, color, and so on, used to draw */ void drawCircle(SkPoint center, SkScalar radius, const SkPaint& paint) { this->drawCircle(center.x(), center.y(), radius, paint); } /** Draws arc using clip, SkMatrix, and SkPaint paint. Arc is part of oval bounded by oval, sweeping from startAngle to startAngle plus sweepAngle. startAngle and sweepAngle are in degrees. startAngle of zero places start point at the right middle edge of oval. A positive sweepAngle places arc end point clockwise from start point; a negative sweepAngle places arc end point counterclockwise from start point. sweepAngle may exceed 360 degrees, a full circle. If useCenter is true, draw a wedge that includes lines from oval center to arc end points. If useCenter is false, draw arc between end points. If SkRect oval is empty or sweepAngle is zero, nothing is drawn. @param oval SkRect bounds of oval containing arc to draw @param startAngle angle in degrees where arc begins @param sweepAngle sweep angle in degrees; positive is clockwise @param useCenter if true, include the center of the oval @param paint SkPaint stroke or fill, blend, color, and so on, used to draw */ void drawArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, const SkPaint& paint); /** Draws SkRRect bounded by SkRect rect, with corner radii (rx, ry) using clip, SkMatrix, and SkPaint paint. In paint: SkPaint::Style determines if SkRRect is stroked or filled; if stroked, SkPaint stroke width describes the line thickness. If rx or ry are less than zero, they are treated as if they are zero. If rx plus ry exceeds rect width or rect height, radii are scaled down to fit. If rx and ry are zero, SkRRect is drawn as SkRect and if stroked is affected by SkPaint::Join. @param rect SkRect bounds of SkRRect to draw @param rx axis length on x-axis of oval describing rounded corners @param ry axis length on y-axis of oval describing rounded corners @param paint stroke, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawRoundRect */ void drawRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, const SkPaint& paint); /** Draws SkPath path using clip, SkMatrix, and SkPaint paint. SkPath contains an array of path contour, each of which may be open or closed. In paint: SkPaint::Style determines if SkRRect is stroked or filled: if filled, SkPath::FillType determines whether path contour describes inside or outside of fill; if stroked, SkPaint stroke width describes the line thickness, SkPaint::Cap describes line ends, and SkPaint::Join describes how corners are drawn. @param path SkPath to draw @param paint stroke, blend, color, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawPath */ void drawPath(const SkPath& path, const SkPaint& paint); void drawImage(const SkImage* image, SkScalar left, SkScalar top) { this->drawImage(image, left, top, SkSamplingOptions(), nullptr); } void drawImage(const sk_sp& image, SkScalar left, SkScalar top) { this->drawImage(image.get(), left, top, SkSamplingOptions(), nullptr); } /** \enum SkCanvas::SrcRectConstraint SrcRectConstraint controls the behavior at the edge of source SkRect, provided to drawImageRect() when there is any filtering. If kStrict is set, then extra code is used to ensure it never samples outside of the src-rect. kStrict_SrcRectConstraint disables the use of mipmaps and anisotropic filtering. */ enum SrcRectConstraint { kStrict_SrcRectConstraint, //!< sample only inside bounds; slower kFast_SrcRectConstraint, //!< sample outside bounds; faster }; void drawImage(const SkImage*, SkScalar x, SkScalar y, const SkSamplingOptions&, const SkPaint* = nullptr); void drawImage(const sk_sp& image, SkScalar x, SkScalar y, const SkSamplingOptions& sampling, const SkPaint* paint = nullptr) { this->drawImage(image.get(), x, y, sampling, paint); } void drawImageRect(const SkImage*, const SkRect& src, const SkRect& dst, const SkSamplingOptions&, const SkPaint*, SrcRectConstraint); void drawImageRect(const SkImage*, const SkRect& dst, const SkSamplingOptions&, const SkPaint* = nullptr); void drawImageRect(const sk_sp& image, const SkRect& src, const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint, SrcRectConstraint constraint) { this->drawImageRect(image.get(), src, dst, sampling, paint, constraint); } void drawImageRect(const sk_sp& image, const SkRect& dst, const SkSamplingOptions& sampling, const SkPaint* paint = nullptr) { this->drawImageRect(image.get(), dst, sampling, paint); } /** Draws SkImage image stretched proportionally to fit into SkRect dst. SkIRect center divides the image into nine sections: four sides, four corners, and the center. Corners are unmodified or scaled down proportionately if their sides are larger than dst; center and four sides are scaled to fit remaining space, if any. Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, and SkBlendMode. If image is kAlpha_8_SkColorType, apply SkShader. If paint contains SkMaskFilter, generate mask from image bounds. Any SkMaskFilter on paint is ignored as is paint anti-aliasing state. If generated mask extends beyond image bounds, replicate image edge colors, just as SkShader made from SkImage::makeShader with SkShader::kClamp_TileMode set replicates the image edge color when it samples outside of its bounds. @param image SkImage containing pixels, dimensions, and format @param center SkIRect edge of image corners and sides @param dst destination SkRect of image to draw to @param filter what technique to use when sampling the image @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, and so on; or nullptr */ void drawImageNine(const SkImage* image, const SkIRect& center, const SkRect& dst, SkFilterMode filter, const SkPaint* paint = nullptr); /** \struct SkCanvas::Lattice SkCanvas::Lattice divides SkBitmap or SkImage into a rectangular grid. Grid entries on even columns and even rows are fixed; these entries are always drawn at their original size if the destination is large enough. If the destination side is too small to hold the fixed entries, all fixed entries are proportionately scaled down to fit. The grid entries not on even columns and rows are scaled to fit the remaining space, if any. */ struct Lattice { /** \enum SkCanvas::Lattice::RectType Optional setting per rectangular grid entry to make it transparent, or to fill the grid entry with a color. */ enum RectType : uint8_t { kDefault = 0, //!< draws SkBitmap into lattice rectangle kTransparent, //!< skips lattice rectangle by making it transparent kFixedColor, //!< draws one of fColors into lattice rectangle }; const int* fXDivs; //!< x-axis values dividing bitmap const int* fYDivs; //!< y-axis values dividing bitmap const RectType* fRectTypes; //!< array of fill types int fXCount; //!< number of x-coordinates int fYCount; //!< number of y-coordinates const SkIRect* fBounds; //!< source bounds to draw from const SkColor* fColors; //!< array of colors }; /** Draws SkImage image stretched proportionally to fit into SkRect dst. SkCanvas::Lattice lattice divides image into a rectangular grid. Each intersection of an even-numbered row and column is fixed; fixed lattice elements never scale larger than their initial size and shrink proportionately when all fixed elements exceed the bitmap dimension. All other grid elements scale to fill the available space, if any. Additionally transform draw using clip, SkMatrix, and optional SkPaint paint. If SkPaint paint is supplied, apply SkColorFilter, alpha, SkImageFilter, and SkBlendMode. If image is kAlpha_8_SkColorType, apply SkShader. If paint contains SkMaskFilter, generate mask from image bounds. Any SkMaskFilter on paint is ignored as is paint anti-aliasing state. If generated mask extends beyond bitmap bounds, replicate bitmap edge colors, just as SkShader made from SkShader::MakeBitmapShader with SkShader::kClamp_TileMode set replicates the bitmap edge color when it samples outside of its bounds. @param image SkImage containing pixels, dimensions, and format @param lattice division of bitmap into fixed and variable rectangles @param dst destination SkRect of image to draw to @param filter what technique to use when sampling the image @param paint SkPaint containing SkBlendMode, SkColorFilter, SkImageFilter, and so on; or nullptr */ void drawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst, SkFilterMode filter, const SkPaint* paint = nullptr); void drawImageLattice(const SkImage* image, const Lattice& lattice, const SkRect& dst) { this->drawImageLattice(image, lattice, dst, SkFilterMode::kNearest, nullptr); } /** * Experimental. Controls anti-aliasing of each edge of images in an image-set. */ enum QuadAAFlags : unsigned { kLeft_QuadAAFlag = 0b0001, kTop_QuadAAFlag = 0b0010, kRight_QuadAAFlag = 0b0100, kBottom_QuadAAFlag = 0b1000, kNone_QuadAAFlags = 0b0000, kAll_QuadAAFlags = 0b1111, }; /** This is used by the experimental API below. */ struct SK_API ImageSetEntry { ImageSetEntry(sk_sp image, const SkRect& srcRect, const SkRect& dstRect, int matrixIndex, float alpha, unsigned aaFlags, bool hasClip); ImageSetEntry(sk_sp image, const SkRect& srcRect, const SkRect& dstRect, float alpha, unsigned aaFlags); ImageSetEntry(); ~ImageSetEntry(); ImageSetEntry(const ImageSetEntry&); ImageSetEntry& operator=(const ImageSetEntry&); sk_sp fImage; SkRect fSrcRect; SkRect fDstRect; int fMatrixIndex = -1; // Index into the preViewMatrices arg, or < 0 float fAlpha = 1.f; unsigned fAAFlags = kNone_QuadAAFlags; // QuadAAFlags bool fHasClip = false; // True to use next 4 points in dstClip arg as quad }; /** * This is an experimental API for the SkiaRenderer Chromium project, and its API will surely * evolve if it is not removed outright. * * This behaves very similarly to drawRect() combined with a clipPath() formed by clip * quadrilateral. 'rect' and 'clip' are in the same coordinate space. If 'clip' is null, then it * is as if the rectangle was not clipped (or, alternatively, clipped to itself). If not null, * then it must provide 4 points. * * In addition to combining the draw and clipping into one operation, this function adds the * additional capability of controlling each of the rectangle's edges anti-aliasing * independently. The edges of the clip will respect the per-edge AA flags. It is required that * 'clip' be contained inside 'rect'. In terms of mapping to edge labels, the 'clip' points * should be ordered top-left, top-right, bottom-right, bottom-left so that the edge between [0] * and [1] is "top", [1] and [2] is "right", [2] and [3] is "bottom", and [3] and [0] is "left". * This ordering matches SkRect::toQuad(). * * This API only draws solid color, filled rectangles so it does not accept a full SkPaint. */ void experimental_DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode); void experimental_DrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags, SkColor color, SkBlendMode mode) { this->experimental_DrawEdgeAAQuad(rect, clip, aaFlags, SkColor4f::FromColor(color), mode); } /** * This is an bulk variant of experimental_DrawEdgeAAQuad() that renders 'cnt' textured quads. * For each entry, 'fDstRect' is rendered with its clip (determined by entry's 'fHasClip' and * the current index in 'dstClip'). The entry's fImage is applied to the destination rectangle * by sampling from 'fSrcRect' sub-image. The corners of 'fSrcRect' map to the corners of * 'fDstRect', just like in drawImageRect(), and they will be properly interpolated when * applying a clip. * * Like experimental_DrawEdgeAAQuad(), each entry can specify edge AA flags that apply to both * the destination rect and its clip. * * If provided, the 'dstClips' array must have length equal 4 * the number of entries with * fHasClip true. If 'dstClips' is null, every entry must have 'fHasClip' set to false. The * destination clip coordinates will be read consecutively with the image set entries, advancing * by 4 points every time an entry with fHasClip is passed. * * This entry point supports per-entry manipulations to the canvas's current matrix. If an * entry provides 'fMatrixIndex' >= 0, it will be drawn as if the canvas's CTM was * canvas->getTotalMatrix() * preViewMatrices[fMatrixIndex]. If 'fMatrixIndex' is less than 0, * the pre-view matrix transform is implicitly the identity, so it will be drawn using just the * current canvas matrix. The pre-view matrix modifies the canvas's view matrix, it does not * affect the local coordinates of each entry. * * An optional paint may be provided, which supports the same subset of features usable with * drawImageRect (i.e. assumed to be filled and no path effects). When a paint is provided, the * image set is drawn as if each image used the applied paint independently, so each is affected * by the image, color, and/or mask filter. */ void experimental_DrawEdgeAAImageSet(const ImageSetEntry imageSet[], int cnt, const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkSamplingOptions&, const SkPaint* paint = nullptr, SrcRectConstraint constraint = kStrict_SrcRectConstraint); /** Draws text, with origin at (x, y), using clip, SkMatrix, SkFont font, and SkPaint paint. When encoding is SkTextEncoding::kUTF8, SkTextEncoding::kUTF16, or SkTextEncoding::kUTF32, this function uses the default character-to-glyph mapping from the SkTypeface in font. It does not perform typeface fallback for characters not found in the SkTypeface. It does not perform kerning or other complex shaping; glyphs are positioned based on their default advances. Text meaning depends on SkTextEncoding. Text size is affected by SkMatrix and SkFont text size. Default text size is 12 point. All elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, and SkImageFilter; apply to text. By default, draws filled black glyphs. @param text character code points or glyphs drawn @param byteLength byte length of text array @param encoding text encoding used in the text array @param x start of text on x-axis @param y start of text on y-axis @param font typeface, text size and so, used to describe the text @param paint blend, color, and so on, used to draw */ void drawSimpleText(const void* text, size_t byteLength, SkTextEncoding encoding, SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint); /** Draws null terminated string, with origin at (x, y), using clip, SkMatrix, SkFont font, and SkPaint paint. This function uses the default character-to-glyph mapping from the SkTypeface in font. It does not perform typeface fallback for characters not found in the SkTypeface. It does not perform kerning; glyphs are positioned based on their default advances. String str is encoded as UTF-8. Text size is affected by SkMatrix and font text size. Default text size is 12 point. All elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, and SkImageFilter; apply to text. By default, draws filled black glyphs. @param str character code points drawn, ending with a char value of zero @param x start of string on x-axis @param y start of string on y-axis @param font typeface, text size and so, used to describe the text @param paint blend, color, and so on, used to draw */ void drawString(const char str[], SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) { this->drawSimpleText(str, strlen(str), SkTextEncoding::kUTF8, x, y, font, paint); } /** Draws SkString, with origin at (x, y), using clip, SkMatrix, SkFont font, and SkPaint paint. This function uses the default character-to-glyph mapping from the SkTypeface in font. It does not perform typeface fallback for characters not found in the SkTypeface. It does not perform kerning; glyphs are positioned based on their default advances. SkString str is encoded as UTF-8. Text size is affected by SkMatrix and SkFont text size. Default text size is 12 point. All elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, and SkImageFilter; apply to text. By default, draws filled black glyphs. @param str character code points drawn, ending with a char value of zero @param x start of string on x-axis @param y start of string on y-axis @param font typeface, text size and so, used to describe the text @param paint blend, color, and so on, used to draw */ void drawString(const SkString& str, SkScalar x, SkScalar y, const SkFont& font, const SkPaint& paint) { this->drawSimpleText(str.c_str(), str.size(), SkTextEncoding::kUTF8, x, y, font, paint); } /** Draws count glyphs, at positions relative to origin styled with font and paint with supporting utf8 and cluster information. This function draw glyphs at the given positions relative to the given origin. It does not perform typeface fallback for glyphs not found in the SkTypeface in font. The drawing obeys the current transform matrix and clipping. All elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, and SkImageFilter; apply to text. By default, draws filled black glyphs. @param count number of glyphs to draw @param glyphs the array of glyphIDs to draw @param positions where to draw each glyph relative to origin @param clusters array of size count of cluster information @param textByteCount size of the utf8text @param utf8text utf8text supporting information for the glyphs @param origin the origin of all the positions @param font typeface, text size and so, used to describe the text @param paint blend, color, and so on, used to draw */ void drawGlyphs(int count, const SkGlyphID glyphs[], const SkPoint positions[], const uint32_t clusters[], int textByteCount, const char utf8text[], SkPoint origin, const SkFont& font, const SkPaint& paint); /** Draws count glyphs, at positions relative to origin styled with font and paint. This function draw glyphs at the given positions relative to the given origin. It does not perform typeface fallback for glyphs not found in the SkTypeface in font. The drawing obeys the current transform matrix and clipping. All elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, and SkImageFilter; apply to text. By default, draws filled black glyphs. @param count number of glyphs to draw @param glyphs the array of glyphIDs to draw @param positions where to draw each glyph relative to origin @param origin the origin of all the positions @param font typeface, text size and so, used to describe the text @param paint blend, color, and so on, used to draw */ void drawGlyphs(int count, const SkGlyphID glyphs[], const SkPoint positions[], SkPoint origin, const SkFont& font, const SkPaint& paint); /** Draws count glyphs, at positions relative to origin styled with font and paint. This function draw glyphs using the given scaling and rotations. They are positioned relative to the given origin. It does not perform typeface fallback for glyphs not found in the SkTypeface in font. The drawing obeys the current transform matrix and clipping. All elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, and SkImageFilter; apply to text. By default, draws filled black glyphs. @param count number of glyphs to draw @param glyphs the array of glyphIDs to draw @param xforms where to draw and orient each glyph @param origin the origin of all the positions @param font typeface, text size and so, used to describe the text @param paint blend, color, and so on, used to draw */ void drawGlyphs(int count, const SkGlyphID glyphs[], const SkRSXform xforms[], SkPoint origin, const SkFont& font, const SkPaint& paint); /** Draws SkTextBlob blob at (x, y), using clip, SkMatrix, and SkPaint paint. blob contains glyphs, their positions, and paint attributes specific to text: SkTypeface, SkPaint text size, SkPaint text scale x, SkPaint text skew x, SkPaint::Align, SkPaint::Hinting, anti-alias, SkPaint fake bold, SkPaint font embedded bitmaps, SkPaint full hinting spacing, LCD text, SkPaint linear text, and SkPaint subpixel text. SkTextEncoding must be set to SkTextEncoding::kGlyphID. Elements of paint: anti-alias, SkBlendMode, color including alpha, SkColorFilter, SkPaint dither, SkMaskFilter, SkPathEffect, SkShader, and SkPaint::Style; apply to blob. If SkPaint contains SkPaint::kStroke_Style: SkPaint miter limit, SkPaint::Cap, SkPaint::Join, and SkPaint stroke width; apply to SkPath created from blob. @param blob glyphs, positions, and their paints' text size, typeface, and so on @param x horizontal offset applied to blob @param y vertical offset applied to blob @param paint blend, color, stroking, and so on, used to draw example: https://fiddle.skia.org/c/@Canvas_drawTextBlob */ void drawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint); /** Draws SkTextBlob blob at (x, y), using clip, SkMatrix, and SkPaint paint. blob contains glyphs, their positions, and paint attributes specific to text: SkTypeface, SkPaint text size, SkPaint text scale x, SkPaint text skew x, SkPaint::Align, SkPaint::Hinting, anti-alias, SkPaint fake bold, SkPaint font embedded bitmaps, SkPaint full hinting spacing, LCD text, SkPaint linear text, and SkPaint subpixel text. SkTextEncoding must be set to SkTextEncoding::kGlyphID. Elements of paint: SkPathEffect, SkMaskFilter, SkShader, SkColorFilter, and SkImageFilter; apply to blob. @param blob glyphs, positions, and their paints' text size, typeface, and so on @param x horizontal offset applied to blob @param y vertical offset applied to blob @param paint blend, color, stroking, and so on, used to draw */ void drawTextBlob(const sk_sp& blob, SkScalar x, SkScalar y, const SkPaint& paint) { this->drawTextBlob(blob.get(), x, y, paint); } /** Draws SkPicture picture, using clip and SkMatrix. Clip and SkMatrix are unchanged by picture contents, as if save() was called before and restore() was called after drawPicture(). SkPicture records a series of draw commands for later playback. @param picture recorded drawing commands to play */ void drawPicture(const SkPicture* picture) { this->drawPicture(picture, nullptr, nullptr); } /** Draws SkPicture picture, using clip and SkMatrix. Clip and SkMatrix are unchanged by picture contents, as if save() was called before and restore() was called after drawPicture(). SkPicture records a series of draw commands for later playback. @param picture recorded drawing commands to play */ void drawPicture(const sk_sp& picture) { this->drawPicture(picture.get()); } /** Draws SkPicture picture, using clip and SkMatrix; transforming picture with SkMatrix matrix, if provided; and use SkPaint paint alpha, SkColorFilter, SkImageFilter, and SkBlendMode, if provided. If paint is non-null, then the picture is always drawn into a temporary layer before actually landing on the canvas. Note that drawing into a layer can also change its appearance if there are any non-associative blendModes inside any of the pictures elements. @param picture recorded drawing commands to play @param matrix SkMatrix to rotate, scale, translate, and so on; may be nullptr @param paint SkPaint to apply transparency, filtering, and so on; may be nullptr example: https://fiddle.skia.org/c/@Canvas_drawPicture_3 */ void drawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint); /** Draws SkPicture picture, using clip and SkMatrix; transforming picture with SkMatrix matrix, if provided; and use SkPaint paint alpha, SkColorFilter, SkImageFilter, and SkBlendMode, if provided. If paint is non-null, then the picture is always drawn into a temporary layer before actually landing on the canvas. Note that drawing into a layer can also change its appearance if there are any non-associative blendModes inside any of the pictures elements. @param picture recorded drawing commands to play @param matrix SkMatrix to rotate, scale, translate, and so on; may be nullptr @param paint SkPaint to apply transparency, filtering, and so on; may be nullptr */ void drawPicture(const sk_sp& picture, const SkMatrix* matrix, const SkPaint* paint) { this->drawPicture(picture.get(), matrix, paint); } /** Draws SkVertices vertices, a triangle mesh, using clip and SkMatrix. If paint contains an SkShader and vertices does not contain texCoords, the shader is mapped using the vertices' positions. SkBlendMode is ignored if SkVertices does not have colors. Otherwise, it combines - the SkShader if SkPaint contains SkShader - or the opaque SkPaint color if SkPaint does not contain SkShader as the src of the blend and the interpolated vertex colors as the dst. SkMaskFilter, SkPathEffect, and antialiasing on SkPaint are ignored. @param vertices triangle mesh to draw @param mode combines vertices' colors with SkShader if present or SkPaint opaque color if not. Ignored if the vertices do not contain color. @param paint specifies the SkShader, used as SkVertices texture, and SkColorFilter. example: https://fiddle.skia.org/c/@Canvas_drawVertices */ void drawVertices(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint); /** Draws SkVertices vertices, a triangle mesh, using clip and SkMatrix. If paint contains an SkShader and vertices does not contain texCoords, the shader is mapped using the vertices' positions. SkBlendMode is ignored if SkVertices does not have colors. Otherwise, it combines - the SkShader if SkPaint contains SkShader - or the opaque SkPaint color if SkPaint does not contain SkShader as the src of the blend and the interpolated vertex colors as the dst. SkMaskFilter, SkPathEffect, and antialiasing on SkPaint are ignored. @param vertices triangle mesh to draw @param mode combines vertices' colors with SkShader if present or SkPaint opaque color if not. Ignored if the vertices do not contain color. @param paint specifies the SkShader, used as SkVertices texture, may be nullptr example: https://fiddle.skia.org/c/@Canvas_drawVertices_2 */ void drawVertices(const sk_sp& vertices, SkBlendMode mode, const SkPaint& paint); /** Experimental, under active development, and subject to change without notice. Draws a mesh using a user-defined specification (see SkMeshSpecification). Requires a GPU backend or SkSL to be compiled in. SkBlender is ignored if SkMesh's specification does not output fragment shader color. Otherwise, it combines - the SkShader if SkPaint contains SkShader - or the opaque SkPaint color if SkPaint does not contain SkShader as the src of the blend and the mesh's fragment color as the dst. SkMaskFilter, SkPathEffect, and antialiasing on SkPaint are ignored. @param mesh the mesh vertices and compatible specification. @param blender combines vertices colors with SkShader if present or SkPaint opaque color if not. Ignored if the custom mesh does not output color. Defaults to SkBlendMode::kModulate if nullptr. @param paint specifies the SkShader, used as SkVertices texture, may be nullptr */ void drawMesh(const SkMesh& mesh, sk_sp blender, const SkPaint& paint); /** Draws a Coons patch: the interpolation of four cubics with shared corners, associating a color, and optionally a texture SkPoint, with each corner. SkPoint array cubics specifies four SkPath cubic starting at the top-left corner, in clockwise order, sharing every fourth point. The last SkPath cubic ends at the first point. Color array color associates colors with corners in top-left, top-right, bottom-right, bottom-left order. If paint contains SkShader, SkPoint array texCoords maps SkShader as texture to corners in top-left, top-right, bottom-right, bottom-left order. If texCoords is nullptr, SkShader is mapped using positions (derived from cubics). SkBlendMode is ignored if colors is null. Otherwise, it combines - the SkShader if SkPaint contains SkShader - or the opaque SkPaint color if SkPaint does not contain SkShader as the src of the blend and the interpolated patch colors as the dst. SkMaskFilter, SkPathEffect, and antialiasing on SkPaint are ignored. @param cubics SkPath cubic array, sharing common points @param colors color array, one for each corner @param texCoords SkPoint array of texture coordinates, mapping SkShader to corners; may be nullptr @param mode combines patch's colors with SkShader if present or SkPaint opaque color if not. Ignored if colors is null. @param paint SkShader, SkColorFilter, SkBlendMode, used to draw */ void drawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint); /** Draws a set of sprites from atlas, using clip, SkMatrix, and optional SkPaint paint. paint uses anti-alias, alpha, SkColorFilter, SkImageFilter, and SkBlendMode to draw, if present. For each entry in the array, SkRect tex locates sprite in atlas, and SkRSXform xform transforms it into destination space. SkMaskFilter and SkPathEffect on paint are ignored. xform, tex, and colors if present, must contain count entries. Optional colors are applied for each sprite using SkBlendMode mode, treating sprite as source and colors as destination. Optional cullRect is a conservative bounds of all transformed sprites. If cullRect is outside of clip, canvas can skip drawing. If atlas is nullptr, this draws nothing. @param atlas SkImage containing sprites @param xform SkRSXform mappings for sprites in atlas @param tex SkRect locations of sprites in atlas @param colors one per sprite, blended with sprite using SkBlendMode; may be nullptr @param count number of sprites to draw @param mode SkBlendMode combining colors and sprites @param sampling SkSamplingOptions used when sampling from the atlas image @param cullRect bounds of transformed sprites for efficient clipping; may be nullptr @param paint SkColorFilter, SkImageFilter, SkBlendMode, and so on; may be nullptr */ void drawAtlas(const SkImage* atlas, const SkRSXform xform[], const SkRect tex[], const SkColor colors[], int count, SkBlendMode mode, const SkSamplingOptions& sampling, const SkRect* cullRect, const SkPaint* paint); /** Draws SkDrawable drawable using clip and SkMatrix, concatenated with optional matrix. If SkCanvas has an asynchronous implementation, as is the case when it is recording into SkPicture, then drawable will be referenced, so that SkDrawable::draw() can be called when the operation is finalized. To force immediate drawing, call SkDrawable::draw() instead. @param drawable custom struct encapsulating drawing commands @param matrix transformation applied to drawing; may be nullptr example: https://fiddle.skia.org/c/@Canvas_drawDrawable */ void drawDrawable(SkDrawable* drawable, const SkMatrix* matrix = nullptr); /** Draws SkDrawable drawable using clip and SkMatrix, offset by (x, y). If SkCanvas has an asynchronous implementation, as is the case when it is recording into SkPicture, then drawable will be referenced, so that SkDrawable::draw() can be called when the operation is finalized. To force immediate drawing, call SkDrawable::draw() instead. @param drawable custom struct encapsulating drawing commands @param x offset into SkCanvas writable pixels on x-axis @param y offset into SkCanvas writable pixels on y-axis example: https://fiddle.skia.org/c/@Canvas_drawDrawable_2 */ void drawDrawable(SkDrawable* drawable, SkScalar x, SkScalar y); /** Associates SkRect on SkCanvas with an annotation; a key-value pair, where the key is a null-terminated UTF-8 string, and optional value is stored as SkData. Only some canvas implementations, such as recording to SkPicture, or drawing to document PDF, use annotations. @param rect SkRect extent of canvas to annotate @param key string used for lookup @param value data holding value stored in annotation example: https://fiddle.skia.org/c/@Canvas_drawAnnotation_2 */ void drawAnnotation(const SkRect& rect, const char key[], SkData* value); /** Associates SkRect on SkCanvas when an annotation; a key-value pair, where the key is a null-terminated UTF-8 string, and optional value is stored as SkData. Only some canvas implementations, such as recording to SkPicture, or drawing to document PDF, use annotations. @param rect SkRect extent of canvas to annotate @param key string used for lookup @param value data holding value stored in annotation */ void drawAnnotation(const SkRect& rect, const char key[], const sk_sp& value) { this->drawAnnotation(rect, key, value.get()); } /** Returns true if clip is empty; that is, nothing will draw. May do work when called; it should not be called more often than needed. However, once called, subsequent calls perform no work until clip changes. @return true if clip is empty example: https://fiddle.skia.org/c/@Canvas_isClipEmpty */ virtual bool isClipEmpty() const; /** Returns true if clip is SkRect and not empty. Returns false if the clip is empty, or if it is not SkRect. @return true if clip is SkRect and not empty example: https://fiddle.skia.org/c/@Canvas_isClipRect */ virtual bool isClipRect() const; /** Returns the current transform from local coordinates to the 'device', which for most * purposes means pixels. * * @return transformation from local coordinates to device / pixels. */ SkM44 getLocalToDevice() const; /** * Throws away the 3rd row and column in the matrix, so be warned. */ SkMatrix getLocalToDeviceAs3x3() const { return this->getLocalToDevice().asM33(); } #ifdef SK_SUPPORT_LEGACY_GETTOTALMATRIX /** DEPRECATED * Legacy version of getLocalToDevice(), which strips away any Z information, and * just returns a 3x3 version. * * @return 3x3 version of getLocalToDevice() * * example: https://fiddle.skia.org/c/@Canvas_getTotalMatrix * example: https://fiddle.skia.org/c/@Clip */ SkMatrix getTotalMatrix() const; #endif /////////////////////////////////////////////////////////////////////////// /** * Returns the global clip as a region. If the clip contains AA, then only the bounds * of the clip may be returned. */ void temporary_internal_getRgnClip(SkRegion* region); void private_draw_shadow_rec(const SkPath&, const SkDrawShadowRec&); protected: // default impl defers to getDevice()->newSurface(info) virtual sk_sp onNewSurface(const SkImageInfo& info, const SkSurfaceProps& props); // default impl defers to its device virtual bool onPeekPixels(SkPixmap* pixmap); virtual bool onAccessTopLayerPixels(SkPixmap* pixmap); virtual SkImageInfo onImageInfo() const; virtual bool onGetProps(SkSurfaceProps* props, bool top) const; // Subclass save/restore notifiers. // Overriders should call the corresponding INHERITED method up the inheritance chain. // getSaveLayerStrategy()'s return value may suppress full layer allocation. enum SaveLayerStrategy { kFullLayer_SaveLayerStrategy, kNoLayer_SaveLayerStrategy, }; virtual void willSave() {} // Overriders should call the corresponding INHERITED method up the inheritance chain. virtual SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& ) { return kFullLayer_SaveLayerStrategy; } // returns true if we should actually perform the saveBehind, or false if we should just save. virtual bool onDoSaveBehind(const SkRect*) { return true; } virtual void willRestore() {} virtual void didRestore() {} virtual void didConcat44(const SkM44&) {} virtual void didSetM44(const SkM44&) {} virtual void didTranslate(SkScalar, SkScalar) {} virtual void didScale(SkScalar, SkScalar) {} // NOTE: If you are adding a new onDraw virtual to SkCanvas, PLEASE add an override to // SkCanvasVirtualEnforcer (in SkCanvasVirtualEnforcer.h). This ensures that subclasses using // that mechanism will be required to implement the new function. virtual void onDrawPaint(const SkPaint& paint); virtual void onDrawBehind(const SkPaint& paint); virtual void onDrawRect(const SkRect& rect, const SkPaint& paint); virtual void onDrawRRect(const SkRRect& rrect, const SkPaint& paint); virtual void onDrawDRRect(const SkRRect& outer, const SkRRect& inner, const SkPaint& paint); virtual void onDrawOval(const SkRect& rect, const SkPaint& paint); virtual void onDrawArc(const SkRect& rect, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, const SkPaint& paint); virtual void onDrawPath(const SkPath& path, const SkPaint& paint); virtual void onDrawRegion(const SkRegion& region, const SkPaint& paint); virtual void onDrawTextBlob(const SkTextBlob* blob, SkScalar x, SkScalar y, const SkPaint& paint); virtual void onDrawGlyphRunList(const sktext::GlyphRunList& glyphRunList, const SkPaint& paint); virtual void onDrawPatch(const SkPoint cubics[12], const SkColor colors[4], const SkPoint texCoords[4], SkBlendMode mode, const SkPaint& paint); virtual void onDrawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint); virtual void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&, const SkPaint*); virtual void onDrawImageRect2(const SkImage*, const SkRect& src, const SkRect& dst, const SkSamplingOptions&, const SkPaint*, SrcRectConstraint); virtual void onDrawImageLattice2(const SkImage*, const Lattice&, const SkRect& dst, SkFilterMode, const SkPaint*); virtual void onDrawAtlas2(const SkImage*, const SkRSXform[], const SkRect src[], const SkColor[], int count, SkBlendMode, const SkSamplingOptions&, const SkRect* cull, const SkPaint*); virtual void onDrawEdgeAAImageSet2(const ImageSetEntry imageSet[], int count, const SkPoint dstClips[], const SkMatrix preViewMatrices[], const SkSamplingOptions&, const SkPaint*, SrcRectConstraint); virtual void onDrawVerticesObject(const SkVertices* vertices, SkBlendMode mode, const SkPaint& paint); virtual void onDrawMesh(const SkMesh&, sk_sp, const SkPaint&); virtual void onDrawAnnotation(const SkRect& rect, const char key[], SkData* value); virtual void onDrawShadowRec(const SkPath&, const SkDrawShadowRec&); virtual void onDrawDrawable(SkDrawable* drawable, const SkMatrix* matrix); virtual void onDrawPicture(const SkPicture* picture, const SkMatrix* matrix, const SkPaint* paint); virtual void onDrawEdgeAAQuad(const SkRect& rect, const SkPoint clip[4], QuadAAFlags aaFlags, const SkColor4f& color, SkBlendMode mode); enum ClipEdgeStyle { kHard_ClipEdgeStyle, kSoft_ClipEdgeStyle }; virtual void onClipRect(const SkRect& rect, SkClipOp op, ClipEdgeStyle edgeStyle); virtual void onClipRRect(const SkRRect& rrect, SkClipOp op, ClipEdgeStyle edgeStyle); virtual void onClipPath(const SkPath& path, SkClipOp op, ClipEdgeStyle edgeStyle); virtual void onClipShader(sk_sp, SkClipOp); virtual void onClipRegion(const SkRegion& deviceRgn, SkClipOp op); virtual void onResetClip(); virtual void onDiscard(); /** */ virtual sk_sp onConvertGlyphRunListToSlug( const sktext::GlyphRunList& glyphRunList, const SkPaint& paint); /** */ virtual void onDrawSlug(const sktext::gpu::Slug* slug); private: enum ShaderOverrideOpacity { kNone_ShaderOverrideOpacity, //!< there is no overriding shader (bitmap or image) kOpaque_ShaderOverrideOpacity, //!< the overriding shader is opaque kNotOpaque_ShaderOverrideOpacity, //!< the overriding shader may not be opaque }; // notify our surface (if we have one) that we are about to draw, so it // can perform copy-on-write or invalidate any cached images // returns false if the copy failed [[nodiscard]] bool predrawNotify(bool willOverwritesEntireSurface = false); [[nodiscard]] bool predrawNotify(const SkRect*, const SkPaint*, ShaderOverrideOpacity); enum class CheckForOverwrite : bool { kNo = false, kYes = true }; // call the appropriate predrawNotify and create a layer if needed. std::optional aboutToDraw( SkCanvas* canvas, const SkPaint& paint, const SkRect* rawBounds = nullptr, CheckForOverwrite = CheckForOverwrite::kNo, ShaderOverrideOpacity = kNone_ShaderOverrideOpacity); // The bottom-most device in the stack, only changed by init(). Image properties and the final // canvas pixels are determined by this device. SkDevice* rootDevice() const { SkASSERT(fRootDevice); return fRootDevice.get(); } // The top-most device in the stack, will change within saveLayer()'s. All drawing and clipping // operations should route to this device. SkDevice* topDevice() const; // Canvases maintain a sparse stack of layers, where the top-most layer receives the drawing, // clip, and matrix commands. There is a layer per call to saveLayer() using the // kFullLayer_SaveLayerStrategy. struct Layer { sk_sp fDevice; sk_sp fImageFilter; // applied to layer *before* being drawn by paint SkPaint fPaint; bool fIsCoverage; bool fDiscard; Layer(sk_sp device, sk_sp imageFilter, const SkPaint& paint, bool isCoverage); }; // Encapsulate state needed to restore from saveBehind() struct BackImage { // Out of line to avoid including SkSpecialImage.h BackImage(sk_sp, SkIPoint); BackImage(const BackImage&); BackImage(BackImage&&); BackImage& operator=(const BackImage&); ~BackImage(); sk_sp fImage; SkIPoint fLoc; }; class MCRec { public: // If not null, this MCRec corresponds with the saveLayer() record that made the layer. // The base "layer" is not stored here, since it is stored inline in SkCanvas and has no // restoration behavior. std::unique_ptr fLayer; // This points to the device of the top-most layer (which may be lower in the stack), or // to the canvas's fRootDevice. The MCRec does not own the device. SkDevice* fDevice; std::unique_ptr fBackImage; SkM44 fMatrix; int fDeferredSaveCount = 0; MCRec(SkDevice* device); MCRec(const MCRec* prev); ~MCRec(); void newLayer(sk_sp layerDevice, sk_sp filter, const SkPaint& restorePaint, bool layerIsCoverage); void reset(SkDevice* device); }; // the first N recs that can fit here mean we won't call malloc static constexpr int kMCRecSize = 96; // most recent measurement static constexpr int kMCRecCount = 32; // common depth for save/restores intptr_t fMCRecStorage[kMCRecSize * kMCRecCount / sizeof(intptr_t)]; SkDeque fMCStack; // points to top of stack MCRec* fMCRec; // Installed via init() sk_sp fRootDevice; const SkSurfaceProps fProps; int fSaveCount; // value returned by getSaveCount() std::unique_ptr fAllocator; SkSurface_Base* fSurfaceBase; SkSurface_Base* getSurfaceBase() const { return fSurfaceBase; } void setSurfaceBase(SkSurface_Base* sb) { fSurfaceBase = sb; } friend class SkSurface_Base; friend class SkSurface_Ganesh; SkIRect fClipRestrictionRect = SkIRect::MakeEmpty(); int fClipRestrictionSaveCount = -1; void doSave(); void checkForDeferredSave(); void internalSetMatrix(const SkM44&); friend class SkAndroidFrameworkUtils; friend class SkCanvasPriv; // needs to expose android functions for testing outside android friend class AutoLayerForImageFilter; friend class SkSurface_Raster; // needs getDevice() friend class SkNoDrawCanvas; // needs resetForNextPicture() friend class SkNWayCanvas; friend class SkPictureRecord; // predrawNotify (why does it need it? ) friend class SkOverdrawCanvas; friend class SkRasterHandleAllocator; friend class SkRecords::Draw; template friend class SkTestCanvas; protected: // For use by SkNoDrawCanvas (via SkCanvasVirtualEnforcer, which can't be a friend) SkCanvas(const SkIRect& bounds); private: SkCanvas(const SkBitmap&, std::unique_ptr, SkRasterHandleAllocator::Handle, const SkSurfaceProps* props); SkCanvas(SkCanvas&&) = delete; SkCanvas(const SkCanvas&) = delete; SkCanvas& operator=(SkCanvas&&) = delete; SkCanvas& operator=(const SkCanvas&) = delete; friend class sktext::gpu::Slug; friend class SkPicturePlayback; /** * Convert a SkTextBlob to a sktext::gpu::Slug using the current canvas state. */ sk_sp convertBlobToSlug(const SkTextBlob& blob, SkPoint origin, const SkPaint& paint); /** * Draw an sktext::gpu::Slug given the current canvas state. */ void drawSlug(const sktext::gpu::Slug* slug); /** Experimental * Saves the specified subset of the current pixels in the current layer, * and then clears those pixels to transparent black. * Restores the pixels on restore() by drawing them in SkBlendMode::kDstOver. * * @param subset conservative bounds of the area to be saved / restored. * @return depth of save state stack before this call was made. */ int only_axis_aligned_saveBehind(const SkRect* subset); /** * Like drawPaint, but magically clipped to the most recent saveBehind buffer rectangle. * If there is no active saveBehind, then this draws nothing. */ void drawClippedToSaveBehind(const SkPaint&); void resetForNextPicture(const SkIRect& bounds); // needs gettotalclip() friend class SkCanvasStateUtils; void init(sk_sp); // All base onDrawX() functions should call this and skip drawing if it returns true. // If 'matrix' is non-null, it maps the paint's fast bounds before checking for quick rejection bool internalQuickReject(const SkRect& bounds, const SkPaint& paint, const SkMatrix* matrix = nullptr); void internalDrawPaint(const SkPaint& paint); void internalSaveLayer(const SaveLayerRec&, SaveLayerStrategy, bool coverageOnly=false); void internalSaveBehind(const SkRect*); void internalConcat44(const SkM44&); // shared by save() and saveLayer() void internalSave(); void internalRestore(); enum class DeviceCompatibleWithFilter : bool { // Check the src device's local-to-device matrix for compatibility with the filter, and if // it is not compatible, introduce an intermediate image and transformation that allows the // filter to be evaluated on the modified src content. kUnknown = false, // Assume that the src device's local-to-device matrix is compatible with the filter. kYes = true }; /** * Filters the contents of 'src' and draws the result into 'dst'. The filter is evaluated * relative to the current canvas matrix, and src is drawn to dst using their relative transform * 'paint' is applied after the filter and must not have a mask or image filter of its own. * A null 'filter' behaves as if the identity filter were used. * * 'scaleFactor' is an extra uniform scale transform applied to downscale the 'src' image * before any filtering, or as part of the copy, and is then drawn with 1/scaleFactor to 'dst'. * Must be 1.0 if 'compat' is kYes (i.e. any scale factor has already been baked into the * relative transforms between the devices). */ void internalDrawDeviceWithFilter(SkDevice* src, SkDevice* dst, const SkImageFilter* filter, const SkPaint& paint, DeviceCompatibleWithFilter compat, SkScalar scaleFactor = 1.f, bool srcIsCoverageLayer = false); /* * Returns true if drawing the specified rect (or all if it is null) with the specified * paint (or default if null) would overwrite the entire root device of the canvas * (i.e. the canvas' surface if it had one). */ bool wouldOverwriteEntireSurface(const SkRect*, const SkPaint*, ShaderOverrideOpacity) const; /** * Returns true if the paint's imagefilter can be invoked directly, without needed a layer. */ bool canDrawBitmapAsSprite(SkScalar x, SkScalar y, int w, int h, const SkSamplingOptions&, const SkPaint&); /** * Returns true if the clip (for any active layer) contains antialiasing. * If the clip is empty, this will return false. */ bool androidFramework_isClipAA() const; /** * Reset the clip to be wide-open (modulo any separately specified device clip restriction). * This operate within the save/restore clip stack so it can be undone by restoring to an * earlier save point. */ void internal_private_resetClip(); virtual SkPaintFilterCanvas* internal_private_asPaintFilterCanvas() const { return nullptr; } // Keep track of the device clip bounds in the canvas' global space to reject draws before // invoking the top-level device. SkRect fQuickRejectBounds; // Compute the clip's bounds based on all clipped SkDevice's reported device bounds transformed // into the canvas' global space. SkRect computeDeviceClipBounds(bool outsetForAA=true) const; class AutoUpdateQRBounds; void validateClip() const; std::unique_ptr fScratchGlyphRunBuilder; }; /** \class SkAutoCanvasRestore Stack helper class calls SkCanvas::restoreToCount when SkAutoCanvasRestore goes out of scope. Use this to guarantee that the canvas is restored to a known state. */ class SkAutoCanvasRestore { public: /** Preserves SkCanvas::save() count. Optionally saves SkCanvas clip and SkCanvas matrix. @param canvas SkCanvas to guard @param doSave call SkCanvas::save() @return utility to restore SkCanvas state on destructor */ SkAutoCanvasRestore(SkCanvas* canvas, bool doSave) : fCanvas(canvas), fSaveCount(0) { if (fCanvas) { fSaveCount = canvas->getSaveCount(); if (doSave) { canvas->save(); } } } /** Restores SkCanvas to saved state. Destructor is called when container goes out of scope. */ ~SkAutoCanvasRestore() { if (fCanvas) { fCanvas->restoreToCount(fSaveCount); } } /** Restores SkCanvas to saved state immediately. Subsequent calls and ~SkAutoCanvasRestore() have no effect. */ void restore() { if (fCanvas) { fCanvas->restoreToCount(fSaveCount); fCanvas = nullptr; } } private: SkCanvas* fCanvas; int fSaveCount; SkAutoCanvasRestore(SkAutoCanvasRestore&&) = delete; SkAutoCanvasRestore(const SkAutoCanvasRestore&) = delete; SkAutoCanvasRestore& operator=(SkAutoCanvasRestore&&) = delete; SkAutoCanvasRestore& operator=(const SkAutoCanvasRestore&) = delete; }; class SkStream; /** * SkData holds an immutable data buffer. Not only is the data immutable, * but the actual ptr that is returned (by data() or bytes()) is guaranteed * to always be the same for the life of this instance. */ class SK_API SkData final : public SkNVRefCnt { public: /** * Returns the number of bytes stored. */ size_t size() const { return fSize; } bool isEmpty() const { return 0 == fSize; } /** * Returns the ptr to the data. */ const void* data() const { return fPtr; } /** * Like data(), returns a read-only ptr into the data, but in this case * it is cast to uint8_t*, to make it easy to add an offset to it. */ const uint8_t* bytes() const { return reinterpret_cast(fPtr); } /** * USE WITH CAUTION. * This call will assert that the refcnt is 1, as a precaution against modifying the * contents when another client/thread has access to the data. */ void* writable_data() { if (fSize) { // only assert we're unique if we're not empty SkASSERT(this->unique()); } return const_cast(fPtr); } /** * Helper to copy a range of the data into a caller-provided buffer. * Returns the actual number of bytes copied, after clamping offset and * length to the size of the data. If buffer is NULL, it is ignored, and * only the computed number of bytes is returned. */ size_t copyRange(size_t offset, size_t length, void* buffer) const; /** * Returns true if these two objects have the same length and contents, * effectively returning 0 == memcmp(...) */ bool equals(const SkData* other) const; /** * Function that, if provided, will be called when the SkData goes out * of scope, allowing for custom allocation/freeing of the data's contents. */ typedef void (*ReleaseProc)(const void* ptr, void* context); /** * Create a new dataref by copying the specified data */ static sk_sp MakeWithCopy(const void* data, size_t length); /** * Create a new data with uninitialized contents. The caller should call writable_data() * to write into the buffer, but this must be done before another ref() is made. */ static sk_sp MakeUninitialized(size_t length); /** * Create a new data with zero-initialized contents. The caller should call writable_data() * to write into the buffer, but this must be done before another ref() is made. */ static sk_sp MakeZeroInitialized(size_t length); /** * Create a new dataref by copying the specified c-string * (a null-terminated array of bytes). The returned SkData will have size() * equal to strlen(cstr) + 1. If cstr is NULL, it will be treated the same * as "". */ static sk_sp MakeWithCString(const char cstr[]); /** * Create a new dataref, taking the ptr as is, and using the * releaseproc to free it. The proc may be NULL. */ static sk_sp MakeWithProc(const void* ptr, size_t length, ReleaseProc proc, void* ctx); /** * Call this when the data parameter is already const and will outlive the lifetime of the * SkData. Suitable for with const globals. */ static sk_sp MakeWithoutCopy(const void* data, size_t length) { return MakeWithProc(data, length, NoopReleaseProc, nullptr); } /** * Create a new dataref from a pointer allocated by malloc. The Data object * takes ownership of that allocation, and will handling calling sk_free. */ static sk_sp MakeFromMalloc(const void* data, size_t length); /** * Create a new dataref the file with the specified path. * If the file cannot be opened, this returns NULL. */ static sk_sp MakeFromFileName(const char path[]); /** * Create a new dataref from a stdio FILE. * This does not take ownership of the FILE, nor close it. * The caller is free to close the FILE at its convenience. * The FILE must be open for reading only. * Returns NULL on failure. */ static sk_sp MakeFromFILE(FILE* f); /** * Create a new dataref from a file descriptor. * This does not take ownership of the file descriptor, nor close it. * The caller is free to close the file descriptor at its convenience. * The file descriptor must be open for reading only. * Returns NULL on failure. */ static sk_sp MakeFromFD(int fd); /** * Attempt to read size bytes into a SkData. If the read succeeds, return the data, * else return NULL. Either way the stream's cursor may have been changed as a result * of calling read(). */ static sk_sp MakeFromStream(SkStream*, size_t size); /** * Create a new dataref using a subset of the data in the specified * src dataref. */ static sk_sp MakeSubset(const SkData* src, size_t offset, size_t length); /** * Returns a new empty dataref (or a reference to a shared empty dataref). * New or shared, the caller must see that unref() is eventually called. */ static sk_sp MakeEmpty(); private: friend class SkNVRefCnt; ReleaseProc fReleaseProc; void* fReleaseProcContext; const void* fPtr; size_t fSize; SkData(const void* ptr, size_t size, ReleaseProc, void* context); explicit SkData(size_t size); // inplace new/delete ~SkData(); // Ensure the unsized delete is called. void operator delete(void* p); // shared internal factory static sk_sp PrivateNewWithCopy(const void* srcOrNull, size_t length); static void NoopReleaseProc(const void*, void*); // {} using INHERITED = SkRefCnt; }; // The bulk of this code is cribbed from: // http://clang.llvm.org/docs/ThreadSafetyAnalysis.html #if defined(__clang__) && (!defined(SWIG)) #define SK_THREAD_ANNOTATION_ATTRIBUTE(x) __attribute__((x)) #else #define SK_THREAD_ANNOTATION_ATTRIBUTE(x) // no-op #endif #define SK_CAPABILITY(x) \ SK_THREAD_ANNOTATION_ATTRIBUTE(capability(x)) #define SK_SCOPED_CAPABILITY \ SK_THREAD_ANNOTATION_ATTRIBUTE(scoped_lockable) #define SK_GUARDED_BY(x) \ SK_THREAD_ANNOTATION_ATTRIBUTE(guarded_by(x)) #define SK_PT_GUARDED_BY(x) \ SK_THREAD_ANNOTATION_ATTRIBUTE(pt_guarded_by(x)) #define SK_ACQUIRED_BEFORE(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(acquired_before(__VA_ARGS__)) #define SK_ACQUIRED_AFTER(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(acquired_after(__VA_ARGS__)) #define SK_REQUIRES(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(requires_capability(__VA_ARGS__)) #define SK_REQUIRES_SHARED(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(requires_shared_capability(__VA_ARGS__)) #define SK_ACQUIRE(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(acquire_capability(__VA_ARGS__)) #define SK_ACQUIRE_SHARED(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(acquire_shared_capability(__VA_ARGS__)) // Would be SK_RELEASE, but that is already in use as SK_DEBUG vs. SK_RELEASE. #define SK_RELEASE_CAPABILITY(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(release_capability(__VA_ARGS__)) // For symmetry with SK_RELEASE_CAPABILITY. #define SK_RELEASE_SHARED_CAPABILITY(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(release_shared_capability(__VA_ARGS__)) #define SK_TRY_ACQUIRE(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(try_acquire_capability(__VA_ARGS__)) #define SK_TRY_ACQUIRE_SHARED(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(try_acquire_shared_capability(__VA_ARGS__)) #define SK_EXCLUDES(...) \ SK_THREAD_ANNOTATION_ATTRIBUTE(locks_excluded(__VA_ARGS__)) #define SK_ASSERT_CAPABILITY(x) \ SK_THREAD_ANNOTATION_ATTRIBUTE(assert_capability(x)) #define SK_ASSERT_SHARED_CAPABILITY(x) \ SK_THREAD_ANNOTATION_ATTRIBUTE(assert_shared_capability(x)) #define SK_RETURN_CAPABILITY(x) \ SK_THREAD_ANNOTATION_ATTRIBUTE(lock_returned(x)) #define SK_NO_THREAD_SAFETY_ANALYSIS \ SK_THREAD_ANNOTATION_ATTRIBUTE(no_thread_safety_analysis) #if defined(SK_BUILD_FOR_GOOGLE3) && !defined(SK_BUILD_FOR_WASM_IN_GOOGLE3) \ && !defined(SK_BUILD_FOR_WIN) extern "C" { void __google_cxa_guard_acquire_begin(void); void __google_cxa_guard_acquire_end (void); } #define SK_POTENTIALLY_BLOCKING_REGION_BEGIN __google_cxa_guard_acquire_begin() #define SK_POTENTIALLY_BLOCKING_REGION_END __google_cxa_guard_acquire_end() #else #define SK_POTENTIALLY_BLOCKING_REGION_BEGIN #define SK_POTENTIALLY_BLOCKING_REGION_END #endif // SkOnce provides call-once guarantees for Skia, much like std::once_flag/std::call_once(). // // There should be no particularly error-prone gotcha use cases when using SkOnce. // It works correctly as a class member, a local, a global, a function-scoped static, whatever. class SkOnce { public: constexpr SkOnce() = default; template void operator()(Fn&& fn, Args&&... args) { auto state = fState.load(std::memory_order_acquire); if (state == Done) { return; } // If it looks like no one has started calling fn(), try to claim that job. if (state == NotStarted && fState.compare_exchange_strong(state, Claimed, std::memory_order_relaxed, std::memory_order_relaxed)) { // Great! We'll run fn() then notify the other threads by releasing Done into fState. fn(std::forward(args)...); return fState.store(Done, std::memory_order_release); } // Some other thread is calling fn(). // We'll just spin here acquiring until it releases Done into fState. SK_POTENTIALLY_BLOCKING_REGION_BEGIN; while (fState.load(std::memory_order_acquire) != Done) { /*spin*/ } SK_POTENTIALLY_BLOCKING_REGION_END; } private: enum State : uint8_t { NotStarted, Claimed, Done}; std::atomic fState{NotStarted}; }; #ifndef SkSemaphore_DEFINED #define SkSemaphore_DEFINED class SkSemaphore { public: constexpr SkSemaphore(int count = 0) : fCount(count), fOSSemaphore(nullptr) {} // Cleanup the underlying OS semaphore. SK_SPI ~SkSemaphore(); // Increment the counter n times. // Generally it's better to call signal(n) instead of signal() n times. void signal(int n = 1); // Decrement the counter by 1, // then if the counter is < 0, sleep this thread until the counter is >= 0. void wait(); // If the counter is positive, decrement it by 1 and return true, otherwise return false. SK_SPI bool try_wait(); private: // This implementation follows the general strategy of // 'A Lightweight Semaphore with Partial Spinning' // found here // http://preshing.com/20150316/semaphores-are-surprisingly-versatile/ // That article (and entire blog) are very much worth reading. // // We wrap an OS-provided semaphore with a user-space atomic counter that // lets us avoid interacting with the OS semaphore unless strictly required: // moving the count from >=0 to <0 or vice-versa, i.e. sleeping or waking threads. struct OSSemaphore; SK_SPI void osSignal(int n); SK_SPI void osWait(); std::atomic fCount; SkOnce fOSSemaphoreOnce; OSSemaphore* fOSSemaphore; }; inline void SkSemaphore::signal(int n) { int prev = fCount.fetch_add(n, std::memory_order_release); // We only want to call the OS semaphore when our logical count crosses // from <0 to >=0 (when we need to wake sleeping threads). // // This is easiest to think about with specific examples of prev and n. // If n == 5 and prev == -3, there are 3 threads sleeping and we signal // std::min(-(-3), 5) == 3 times on the OS semaphore, leaving the count at 2. // // If prev >= 0, no threads are waiting, std::min(-prev, n) is always <= 0, // so we don't call the OS semaphore, leaving the count at (prev + n). int toSignal = std::min(-prev, n); if (toSignal > 0) { this->osSignal(toSignal); } } inline void SkSemaphore::wait() { // Since this fetches the value before the subtract, zero and below means that there are no // resources left, so the thread needs to wait. if (fCount.fetch_sub(1, std::memory_order_acquire) <= 0) { SK_POTENTIALLY_BLOCKING_REGION_BEGIN; this->osWait(); SK_POTENTIALLY_BLOCKING_REGION_END; } } #endif//SkSemaphore_DEFINED typedef int64_t SkThreadID; // SkMutex.h uses SkGetThreadID in debug only code. SkDEBUGCODE(SK_SPI) SkThreadID SkGetThreadID(); const SkThreadID kIllegalThreadID = 0; class SK_CAPABILITY("mutex") SkMutex { public: constexpr SkMutex() = default; ~SkMutex() { this->assertNotHeld(); } void acquire() SK_ACQUIRE() { fSemaphore.wait(); SkDEBUGCODE(fOwner = SkGetThreadID();) } void release() SK_RELEASE_CAPABILITY() { this->assertHeld(); SkDEBUGCODE(fOwner = kIllegalThreadID;) fSemaphore.signal(); } void assertHeld() SK_ASSERT_CAPABILITY(this) { SkASSERT(fOwner == SkGetThreadID()); } void assertNotHeld() { SkASSERT(fOwner == kIllegalThreadID); } private: SkSemaphore fSemaphore{1}; SkDEBUGCODE(SkThreadID fOwner{kIllegalThreadID};) }; class SK_SCOPED_CAPABILITY SkAutoMutexExclusive { public: SkAutoMutexExclusive(SkMutex& mutex) SK_ACQUIRE(mutex) : fMutex(mutex) { fMutex.acquire(); } ~SkAutoMutexExclusive() SK_RELEASE_CAPABILITY() { fMutex.release(); } SkAutoMutexExclusive(const SkAutoMutexExclusive&) = delete; SkAutoMutexExclusive(SkAutoMutexExclusive&&) = delete; SkAutoMutexExclusive& operator=(const SkAutoMutexExclusive&) = delete; SkAutoMutexExclusive& operator=(SkAutoMutexExclusive&&) = delete; private: SkMutex& fMutex; }; /** * Used to be notified when a gen/unique ID is invalidated, typically to preemptively purge * associated items from a cache that are no longer reachable. The listener can * be marked for deregistration if the cached item is remove before the listener is * triggered. This prevents unbounded listener growth when cache items are routinely * removed before the gen ID/unique ID is invalidated. */ class SkIDChangeListener : public SkRefCnt { public: SkIDChangeListener(); ~SkIDChangeListener() override; virtual void changed() = 0; /** * Mark the listener is no longer needed. It should be removed and changed() should not be * called. */ void markShouldDeregister() { fShouldDeregister.store(true, std::memory_order_relaxed); } /** Indicates whether markShouldDeregister was called. */ bool shouldDeregister() { return fShouldDeregister.load(std::memory_order_acquire); } /** Manages a list of SkIDChangeListeners. */ class List { public: List(); ~List(); /** * Add a new listener to the list. It must not already be deregistered. Also clears out * previously deregistered listeners. */ void add(sk_sp listener) SK_EXCLUDES(fMutex); /** * The number of registered listeners (including deregisterd listeners that are yet-to-be * removed. */ int count() const SK_EXCLUDES(fMutex); /** Calls changed() on all listeners that haven't been deregistered and resets the list. */ void changed() SK_EXCLUDES(fMutex); /** Resets without calling changed() on the listeners. */ void reset() SK_EXCLUDES(fMutex); private: mutable SkMutex fMutex; skia_private::STArray<1, sk_sp> fListeners SK_GUARDED_BY(fMutex); }; private: std::atomic fShouldDeregister; }; class SkMatrix; class SkRRect; // These are computed from a stream of verbs struct SkPathVerbAnalysis { bool valid; int points, weights; unsigned segmentMask; }; SkPathVerbAnalysis sk_path_analyze_verbs(const uint8_t verbs[], int count); /** * Holds the path verbs and points. It is versioned by a generation ID. None of its public methods * modify the contents. To modify or append to the verbs/points wrap the SkPathRef in an * SkPathRef::Editor object. Installing the editor resets the generation ID. It also performs * copy-on-write if the SkPathRef is shared by multiple SkPaths. The caller passes the Editor's * constructor a pointer to a sk_sp, which may be updated to point to a new SkPathRef * after the editor's constructor returns. * * The points and verbs are stored in a single allocation. The points are at the begining of the * allocation while the verbs are stored at end of the allocation, in reverse order. Thus the points * and verbs both grow into the middle of the allocation until the meet. To access verb i in the * verb array use ref.verbs()[~i] (because verbs() returns a pointer just beyond the first * logical verb or the last verb in memory). */ class SK_API SkPathRef final : public SkNVRefCnt { public: // See https://bugs.chromium.org/p/skia/issues/detail?id=13817 for how these sizes were // determined. using PointsArray = skia_private::STArray<4, SkPoint>; using VerbsArray = skia_private::STArray<4, uint8_t>; using ConicWeightsArray = skia_private::STArray<2, SkScalar>; SkPathRef(PointsArray points, VerbsArray verbs, ConicWeightsArray weights, unsigned segmentMask) : fPoints(std::move(points)) , fVerbs(std::move(verbs)) , fConicWeights(std::move(weights)) { fBoundsIsDirty = true; // this also invalidates fIsFinite fGenerationID = 0; // recompute fSegmentMask = segmentMask; fIsOval = false; fIsRRect = false; // The next two values don't matter unless fIsOval or fIsRRect are true. fRRectOrOvalIsCCW = false; fRRectOrOvalStartIdx = 0xAC; SkDEBUGCODE(fEditorsAttached.store(0);) this->computeBounds(); // do this now, before we worry about multiple owners/threads SkDEBUGCODE(this->validate();) } class Editor { public: Editor(sk_sp* pathRef, int incReserveVerbs = 0, int incReservePoints = 0); ~Editor() { SkDEBUGCODE(fPathRef->fEditorsAttached--;) } /** * Returns the array of points. */ SkPoint* writablePoints() { return fPathRef->getWritablePoints(); } const SkPoint* points() const { return fPathRef->points(); } /** * Gets the ith point. Shortcut for this->points() + i */ SkPoint* atPoint(int i) { return fPathRef->getWritablePoints() + i; } const SkPoint* atPoint(int i) const { return &fPathRef->fPoints[i]; } /** * Adds the verb and allocates space for the number of points indicated by the verb. The * return value is a pointer to where the points for the verb should be written. * 'weight' is only used if 'verb' is kConic_Verb */ SkPoint* growForVerb(int /*SkPath::Verb*/ verb, SkScalar weight = 0) { SkDEBUGCODE(fPathRef->validate();) return fPathRef->growForVerb(verb, weight); } /** * Allocates space for multiple instances of a particular verb and the * requisite points & weights. * The return pointer points at the first new point (indexed normally []). * If 'verb' is kConic_Verb, 'weights' will return a pointer to the * space for the conic weights (indexed normally). */ SkPoint* growForRepeatedVerb(int /*SkPath::Verb*/ verb, int numVbs, SkScalar** weights = nullptr) { return fPathRef->growForRepeatedVerb(verb, numVbs, weights); } /** * Concatenates all verbs from 'path' onto the pathRef's verbs array. Increases the point * count by the number of points in 'path', and the conic weight count by the number of * conics in 'path'. * * Returns pointers to the uninitialized points and conic weights data. */ std::tuple growForVerbsInPath(const SkPathRef& path) { return fPathRef->growForVerbsInPath(path); } /** * Resets the path ref to a new verb and point count. The new verbs and points are * uninitialized. */ void resetToSize(int newVerbCnt, int newPointCnt, int newConicCount) { fPathRef->resetToSize(newVerbCnt, newPointCnt, newConicCount); } /** * Gets the path ref that is wrapped in the Editor. */ SkPathRef* pathRef() { return fPathRef; } void setIsOval(bool isOval, bool isCCW, unsigned start) { fPathRef->setIsOval(isOval, isCCW, start); } void setIsRRect(bool isRRect, bool isCCW, unsigned start) { fPathRef->setIsRRect(isRRect, isCCW, start); } void setBounds(const SkRect& rect) { fPathRef->setBounds(rect); } private: SkPathRef* fPathRef; }; class SK_API Iter { public: Iter(); Iter(const SkPathRef&); void setPathRef(const SkPathRef&); /** Return the next verb in this iteration of the path. When all segments have been visited, return kDone_Verb. If any point in the path is non-finite, return kDone_Verb immediately. @param pts The points representing the current verb and/or segment This must not be NULL. @return The verb for the current segment */ uint8_t next(SkPoint pts[4]); uint8_t peek() const; SkScalar conicWeight() const { return *fConicWeights; } private: const SkPoint* fPts; const uint8_t* fVerbs; const uint8_t* fVerbStop; const SkScalar* fConicWeights; }; public: /** * Gets a path ref with no verbs or points. */ static SkPathRef* CreateEmpty(); /** * Returns true if all of the points in this path are finite, meaning there * are no infinities and no NaNs. */ bool isFinite() const { if (fBoundsIsDirty) { this->computeBounds(); } return SkToBool(fIsFinite); } /** * Returns a mask, where each bit corresponding to a SegmentMask is * set if the path contains 1 or more segments of that type. * Returns 0 for an empty path (no segments). */ uint32_t getSegmentMasks() const { return fSegmentMask; } /** Returns true if the path is an oval. * * @param rect returns the bounding rect of this oval. It's a circle * if the height and width are the same. * @param isCCW is the oval CCW (or CW if false). * @param start indicates where the contour starts on the oval (see * SkPath::addOval for intepretation of the index). * * @return true if this path is an oval. * Tracking whether a path is an oval is considered an * optimization for performance and so some paths that are in * fact ovals can report false. */ bool isOval(SkRect* rect, bool* isCCW, unsigned* start) const { if (fIsOval) { if (rect) { *rect = this->getBounds(); } if (isCCW) { *isCCW = SkToBool(fRRectOrOvalIsCCW); } if (start) { *start = fRRectOrOvalStartIdx; } } return SkToBool(fIsOval); } bool isRRect(SkRRect* rrect, bool* isCCW, unsigned* start) const; bool hasComputedBounds() const { return !fBoundsIsDirty; } /** Returns the bounds of the path's points. If the path contains 0 or 1 points, the bounds is set to (0,0,0,0), and isEmpty() will return true. Note: this bounds may be larger than the actual shape, since curves do not extend as far as their control points. */ const SkRect& getBounds() const { if (fBoundsIsDirty) { this->computeBounds(); } return fBounds; } SkRRect getRRect() const; /** * Transforms a path ref by a matrix, allocating a new one only if necessary. */ static void CreateTransformedCopy(sk_sp* dst, const SkPathRef& src, const SkMatrix& matrix); // static SkPathRef* CreateFromBuffer(SkRBuffer* buffer); /** * Rollsback a path ref to zero verbs and points with the assumption that the path ref will be * repopulated with approximately the same number of verbs and points. A new path ref is created * only if necessary. */ static void Rewind(sk_sp* pathRef); ~SkPathRef(); int countPoints() const { return fPoints.size(); } int countVerbs() const { return fVerbs.size(); } int countWeights() const { return fConicWeights.size(); } size_t approximateBytesUsed() const; /** * Returns a pointer one beyond the first logical verb (last verb in memory order). */ const uint8_t* verbsBegin() const { return fVerbs.begin(); } /** * Returns a const pointer to the first verb in memory (which is the last logical verb). */ const uint8_t* verbsEnd() const { return fVerbs.end(); } /** * Returns a const pointer to the first point. */ const SkPoint* points() const { return fPoints.begin(); } /** * Shortcut for this->points() + this->countPoints() */ const SkPoint* pointsEnd() const { return this->points() + this->countPoints(); } const SkScalar* conicWeights() const { return fConicWeights.begin(); } const SkScalar* conicWeightsEnd() const { return fConicWeights.end(); } /** * Convenience methods for getting to a verb or point by index. */ uint8_t atVerb(int index) const { return fVerbs[index]; } const SkPoint& atPoint(int index) const { return fPoints[index]; } bool operator== (const SkPathRef& ref) const; void interpolate(const SkPathRef& ending, SkScalar weight, SkPathRef* out) const; /** * Gets an ID that uniquely identifies the contents of the path ref. If two path refs have the * same ID then they have the same verbs and points. However, two path refs may have the same * contents but different genIDs. * skbug.com/1762 for background on why fillType is necessary (for now). */ uint32_t genID(uint8_t fillType) const; void addGenIDChangeListener(sk_sp); // Threadsafe. int genIDChangeListenerCount(); // Threadsafe bool dataMatchesVerbs() const; bool isValid() const; SkDEBUGCODE(void validate() const { SkASSERT(this->isValid()); } ) /** * Resets this SkPathRef to a clean state. */ void reset(); bool isInitialEmptyPathRef() const { return fGenerationID == kEmptyGenID; } private: enum SerializationOffsets { kLegacyRRectOrOvalStartIdx_SerializationShift = 28, // requires 3 bits, ignored. kLegacyRRectOrOvalIsCCW_SerializationShift = 27, // requires 1 bit, ignored. kLegacyIsRRect_SerializationShift = 26, // requires 1 bit, ignored. kIsFinite_SerializationShift = 25, // requires 1 bit kLegacyIsOval_SerializationShift = 24, // requires 1 bit, ignored. kSegmentMask_SerializationShift = 0 // requires 4 bits (deprecated) }; SkPathRef(int numVerbs = 0, int numPoints = 0) { fBoundsIsDirty = true; // this also invalidates fIsFinite fGenerationID = kEmptyGenID; fSegmentMask = 0; fIsOval = false; fIsRRect = false; // The next two values don't matter unless fIsOval or fIsRRect are true. fRRectOrOvalIsCCW = false; fRRectOrOvalStartIdx = 0xAC; if (numPoints > 0) { fPoints.reserve_exact(numPoints); } if (numVerbs > 0) { fVerbs.reserve_exact(numVerbs); } SkDEBUGCODE(fEditorsAttached.store(0);) SkDEBUGCODE(this->validate();) } void copy(const SkPathRef& ref, int additionalReserveVerbs, int additionalReservePoints); // Return true if the computed bounds are finite. static bool ComputePtBounds(SkRect* bounds, const SkPathRef& ref) { return bounds->setBoundsCheck(ref.points(), ref.countPoints()); } // called, if dirty, by getBounds() void computeBounds() const { SkDEBUGCODE(this->validate();) // TODO: remove fBoundsIsDirty and fIsFinite, // using an inverted rect instead of fBoundsIsDirty and always recalculating fIsFinite. SkASSERT(fBoundsIsDirty); fIsFinite = ComputePtBounds(&fBounds, *this); fBoundsIsDirty = false; } void setBounds(const SkRect& rect) { SkASSERT(rect.fLeft <= rect.fRight && rect.fTop <= rect.fBottom); fBounds = rect; fBoundsIsDirty = false; fIsFinite = fBounds.isFinite(); } /** Makes additional room but does not change the counts or change the genID */ void incReserve(int additionalVerbs, int additionalPoints) { SkDEBUGCODE(this->validate();) // Use reserve() so that if there is not enough space, the array will grow with some // additional space. This ensures repeated calls to grow won't always allocate. if (additionalPoints > 0) fPoints.reserve(fPoints.size() + additionalPoints); if (additionalVerbs > 0) fVerbs.reserve(fVerbs.size() + additionalVerbs); SkDEBUGCODE(this->validate();) } /** * Resets all state except that of the verbs, points, and conic-weights. * Intended to be called from other functions that reset state. */ void commonReset() { SkDEBUGCODE(this->validate();) this->callGenIDChangeListeners(); fBoundsIsDirty = true; // this also invalidates fIsFinite fGenerationID = 0; fSegmentMask = 0; fIsOval = false; fIsRRect = false; } /** Resets the path ref with verbCount verbs and pointCount points, all uninitialized. Also * allocates space for reserveVerb additional verbs and reservePoints additional points.*/ void resetToSize(int verbCount, int pointCount, int conicCount, int reserveVerbs = 0, int reservePoints = 0) { this->commonReset(); // Use reserve_exact() so the arrays are sized to exactly fit the data. fPoints.reserve_exact(pointCount + reservePoints); fPoints.resize_back(pointCount); fVerbs.reserve_exact(verbCount + reserveVerbs); fVerbs.resize_back(verbCount); fConicWeights.resize_back(conicCount); SkDEBUGCODE(this->validate();) } /** * Increases the verb count by numVbs and point count by the required amount. * The new points are uninitialized. All the new verbs are set to the specified * verb. If 'verb' is kConic_Verb, 'weights' will return a pointer to the * uninitialized conic weights. */ SkPoint* growForRepeatedVerb(int /*SkPath::Verb*/ verb, int numVbs, SkScalar** weights); /** * Increases the verb count 1, records the new verb, and creates room for the requisite number * of additional points. A pointer to the first point is returned. Any new points are * uninitialized. */ SkPoint* growForVerb(int /*SkPath::Verb*/ verb, SkScalar weight); /** * Concatenates all verbs from 'path' onto our own verbs array. Increases the point count by the * number of points in 'path', and the conic weight count by the number of conics in 'path'. * * Returns pointers to the uninitialized points and conic weights data. */ std::tuple growForVerbsInPath(const SkPathRef& path); /** * Private, non-const-ptr version of the public function verbsMemBegin(). */ uint8_t* verbsBeginWritable() { return fVerbs.begin(); } /** * Called the first time someone calls CreateEmpty to actually create the singleton. */ friend SkPathRef* sk_create_empty_pathref(); void setIsOval(bool isOval, bool isCCW, unsigned start) { fIsOval = isOval; fRRectOrOvalIsCCW = isCCW; fRRectOrOvalStartIdx = SkToU8(start); } void setIsRRect(bool isRRect, bool isCCW, unsigned start) { fIsRRect = isRRect; fRRectOrOvalIsCCW = isCCW; fRRectOrOvalStartIdx = SkToU8(start); } // called only by the editor. Note that this is not a const function. SkPoint* getWritablePoints() { SkDEBUGCODE(this->validate();) fIsOval = false; fIsRRect = false; return fPoints.begin(); } const SkPoint* getPoints() const { SkDEBUGCODE(this->validate();) return fPoints.begin(); } void callGenIDChangeListeners(); enum { kMinSize = 256, }; mutable SkRect fBounds; PointsArray fPoints; VerbsArray fVerbs; ConicWeightsArray fConicWeights; enum { kEmptyGenID = 1, // GenID reserved for path ref with zero points and zero verbs. }; mutable uint32_t fGenerationID; SkDEBUGCODE(std::atomic fEditorsAttached;) // assert only one editor in use at any time. SkIDChangeListener::List fGenIDChangeListeners; mutable uint8_t fBoundsIsDirty; mutable bool fIsFinite; // only meaningful if bounds are valid bool fIsOval; bool fIsRRect; // Both the circle and rrect special cases have a notion of direction and starting point // The next two variables store that information for either. bool fRRectOrOvalIsCCW; uint8_t fRRectOrOvalStartIdx; uint8_t fSegmentMask; friend class PathRefTest_Private; friend class ForceIsRRect_Private; // unit test isRRect friend class SkPath; friend class SkPathBuilder; friend class SkPathPriv; }; class SkRRect; class SK_API SkPathBuilder { public: SkPathBuilder(); SkPathBuilder(SkPathFillType); SkPathBuilder(const SkPath&); SkPathBuilder(const SkPathBuilder&) = default; ~SkPathBuilder(); SkPathBuilder& operator=(const SkPath&); SkPathBuilder& operator=(const SkPathBuilder&) = default; SkPathFillType fillType() const { return fFillType; } SkRect computeBounds() const; SkPath snapshot() const; // the builder is unchanged after returning this path SkPath detach(); // the builder is reset to empty after returning this path SkPathBuilder& setFillType(SkPathFillType ft) { fFillType = ft; return *this; } SkPathBuilder& setIsVolatile(bool isVolatile) { fIsVolatile = isVolatile; return *this; } SkPathBuilder& reset(); SkPathBuilder& moveTo(SkPoint pt); SkPathBuilder& moveTo(SkScalar x, SkScalar y) { return this->moveTo(SkPoint::Make(x, y)); } SkPathBuilder& lineTo(SkPoint pt); SkPathBuilder& lineTo(SkScalar x, SkScalar y) { return this->lineTo(SkPoint::Make(x, y)); } SkPathBuilder& quadTo(SkPoint pt1, SkPoint pt2); SkPathBuilder& quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { return this->quadTo(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2)); } SkPathBuilder& quadTo(const SkPoint pts[2]) { return this->quadTo(pts[0], pts[1]); } SkPathBuilder& conicTo(SkPoint pt1, SkPoint pt2, SkScalar w); SkPathBuilder& conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w) { return this->conicTo(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2), w); } SkPathBuilder& conicTo(const SkPoint pts[2], SkScalar w) { return this->conicTo(pts[0], pts[1], w); } SkPathBuilder& cubicTo(SkPoint pt1, SkPoint pt2, SkPoint pt3); SkPathBuilder& cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3) { return this->cubicTo(SkPoint::Make(x1, y1), SkPoint::Make(x2, y2), SkPoint::Make(x3, y3)); } SkPathBuilder& cubicTo(const SkPoint pts[3]) { return this->cubicTo(pts[0], pts[1], pts[2]); } SkPathBuilder& close(); // Append a series of lineTo(...) SkPathBuilder& polylineTo(const SkPoint pts[], int count); SkPathBuilder& polylineTo(const std::initializer_list& list) { return this->polylineTo(list.begin(), SkToInt(list.size())); } // Relative versions of segments, relative to the previous position. SkPathBuilder& rLineTo(SkPoint pt); SkPathBuilder& rLineTo(SkScalar x, SkScalar y) { return this->rLineTo({x, y}); } SkPathBuilder& rQuadTo(SkPoint pt1, SkPoint pt2); SkPathBuilder& rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { return this->rQuadTo({x1, y1}, {x2, y2}); } SkPathBuilder& rConicTo(SkPoint p1, SkPoint p2, SkScalar w); SkPathBuilder& rConicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w) { return this->rConicTo({x1, y1}, {x2, y2}, w); } SkPathBuilder& rCubicTo(SkPoint pt1, SkPoint pt2, SkPoint pt3); SkPathBuilder& rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3) { return this->rCubicTo({x1, y1}, {x2, y2}, {x3, y3}); } // Arcs /** Appends arc to the builder. Arc added is part of ellipse bounded by oval, from startAngle through sweepAngle. Both startAngle and sweepAngle are measured in degrees, where zero degrees is aligned with the positive x-axis, and positive sweeps extends arc clockwise. arcTo() adds line connecting the builder's last point to initial arc point if forceMoveTo is false and the builder is not empty. Otherwise, added contour begins with first point of arc. Angles greater than -360 and less than 360 are treated modulo 360. @param oval bounds of ellipse containing arc @param startAngleDeg starting angle of arc in degrees @param sweepAngleDeg sweep, in degrees. Positive is clockwise; treated modulo 360 @param forceMoveTo true to start a new contour with arc @return reference to the builder */ SkPathBuilder& arcTo(const SkRect& oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg, bool forceMoveTo); /** Appends arc to SkPath, after appending line if needed. Arc is implemented by conic weighted to describe part of circle. Arc is contained by tangent from last SkPath point to p1, and tangent from p1 to p2. Arc is part of circle sized to radius, positioned so it touches both tangent lines. If last SkPath SkPoint does not start arc, arcTo() appends connecting line to SkPath. The length of vector from p1 to p2 does not affect arc. Arc sweep is always less than 180 degrees. If radius is zero, or if tangents are nearly parallel, arcTo() appends line from last SkPath SkPoint to p1. arcTo() appends at most one line and one conic. arcTo() implements the functionality of PostScript arct and HTML Canvas arcTo. @param p1 SkPoint common to pair of tangents @param p2 end of second tangent @param radius distance from arc to circle center @return reference to SkPath */ SkPathBuilder& arcTo(SkPoint p1, SkPoint p2, SkScalar radius); enum ArcSize { kSmall_ArcSize, //!< smaller of arc pair kLarge_ArcSize, //!< larger of arc pair }; /** Appends arc to SkPath. Arc is implemented by one or more conic weighted to describe part of oval with radii (r.fX, r.fY) rotated by xAxisRotate degrees. Arc curves from last SkPath SkPoint to (xy.fX, xy.fY), choosing one of four possible routes: clockwise or counterclockwise, and smaller or larger. Arc sweep is always less than 360 degrees. arcTo() appends line to xy if either radii are zero, or if last SkPath SkPoint equals (xy.fX, xy.fY). arcTo() scales radii r to fit last SkPath SkPoint and xy if both are greater than zero but too small to describe an arc. arcTo() appends up to four conic curves. arcTo() implements the functionality of SVG arc, although SVG sweep-flag value is opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while kCW_Direction cast to int is zero. @param r radii on axes before x-axis rotation @param xAxisRotate x-axis rotation in degrees; positive values are clockwise @param largeArc chooses smaller or larger arc @param sweep chooses clockwise or counterclockwise arc @param xy end of arc @return reference to SkPath */ SkPathBuilder& arcTo(SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, SkPathDirection sweep, SkPoint xy); /** Appends arc to the builder, as the start of new contour. Arc added is part of ellipse bounded by oval, from startAngle through sweepAngle. Both startAngle and sweepAngle are measured in degrees, where zero degrees is aligned with the positive x-axis, and positive sweeps extends arc clockwise. If sweepAngle <= -360, or sweepAngle >= 360; and startAngle modulo 90 is nearly zero, append oval instead of arc. Otherwise, sweepAngle values are treated modulo 360, and arc may or may not draw depending on numeric rounding. @param oval bounds of ellipse containing arc @param startAngleDeg starting angle of arc in degrees @param sweepAngleDeg sweep, in degrees. Positive is clockwise; treated modulo 360 @return reference to this builder */ SkPathBuilder& addArc(const SkRect& oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg); // Add a new contour SkPathBuilder& addRect(const SkRect&, SkPathDirection, unsigned startIndex); SkPathBuilder& addOval(const SkRect&, SkPathDirection, unsigned startIndex); SkPathBuilder& addRRect(const SkRRect&, SkPathDirection, unsigned startIndex); SkPathBuilder& addRect(const SkRect& rect, SkPathDirection dir = SkPathDirection::kCW) { return this->addRect(rect, dir, 0); } SkPathBuilder& addOval(const SkRect& rect, SkPathDirection dir = SkPathDirection::kCW) { // legacy start index: 1 return this->addOval(rect, dir, 1); } SkPathBuilder& addRRect(const SkRRect& rrect, SkPathDirection dir = SkPathDirection::kCW) { // legacy start indices: 6 (CW) and 7 (CCW) return this->addRRect(rrect, dir, dir == SkPathDirection::kCW ? 6 : 7); } SkPathBuilder& addCircle(SkScalar center_x, SkScalar center_y, SkScalar radius, SkPathDirection dir = SkPathDirection::kCW); SkPathBuilder& addPolygon(const SkPoint pts[], int count, bool isClosed); SkPathBuilder& addPolygon(const std::initializer_list& list, bool isClosed) { return this->addPolygon(list.begin(), SkToInt(list.size()), isClosed); } SkPathBuilder& addPath(const SkPath&); // Performance hint, to reserve extra storage for subsequent calls to lineTo, quadTo, etc. void incReserve(int extraPtCount, int extraVerbCount); void incReserve(int extraPtCount) { this->incReserve(extraPtCount, extraPtCount); } SkPathBuilder& offset(SkScalar dx, SkScalar dy); SkPathBuilder& toggleInverseFillType() { fFillType = (SkPathFillType)((unsigned)fFillType ^ 2); return *this; } private: SkPathRef::PointsArray fPts; SkPathRef::VerbsArray fVerbs; SkPathRef::ConicWeightsArray fConicWeights; SkPathFillType fFillType; bool fIsVolatile; unsigned fSegmentMask; SkPoint fLastMovePoint; int fLastMoveIndex; // only needed until SkPath is immutable bool fNeedsMoveVerb; enum IsA { kIsA_JustMoves, // we only have 0 or more moves kIsA_MoreThanMoves, // we have verbs other than just move kIsA_Oval, // we are 0 or more moves followed by an oval kIsA_RRect, // we are 0 or more moves followed by a rrect }; IsA fIsA = kIsA_JustMoves; int fIsAStart = -1; // tracks direction iff fIsA is not unknown bool fIsACCW = false; // tracks direction iff fIsA is not unknown int countVerbs() const { return fVerbs.size(); } // called right before we add a (non-move) verb void ensureMove() { fIsA = kIsA_MoreThanMoves; if (fNeedsMoveVerb) { this->moveTo(fLastMovePoint); } } SkPath make(sk_sp) const; SkPathBuilder& privateReverseAddPath(const SkPath&); friend class SkPathPriv; }; struct SK_API SkPoint3 { SkScalar fX, fY, fZ; static SkPoint3 Make(SkScalar x, SkScalar y, SkScalar z) { SkPoint3 pt; pt.set(x, y, z); return pt; } SkScalar x() const { return fX; } SkScalar y() const { return fY; } SkScalar z() const { return fZ; } void set(SkScalar x, SkScalar y, SkScalar z) { fX = x; fY = y; fZ = z; } friend bool operator==(const SkPoint3& a, const SkPoint3& b) { return a.fX == b.fX && a.fY == b.fY && a.fZ == b.fZ; } friend bool operator!=(const SkPoint3& a, const SkPoint3& b) { return !(a == b); } /** Returns the Euclidian distance from (0,0,0) to (x,y,z) */ static SkScalar Length(SkScalar x, SkScalar y, SkScalar z); /** Return the Euclidian distance from (0,0,0) to the point */ SkScalar length() const { return SkPoint3::Length(fX, fY, fZ); } /** Set the point (vector) to be unit-length in the same direction as it already points. If the point has a degenerate length (i.e., nearly 0) then set it to (0,0,0) and return false; otherwise return true. */ bool normalize(); /** Return a new point whose X, Y and Z coordinates are scaled. */ SkPoint3 makeScale(SkScalar scale) const { SkPoint3 p; p.set(scale * fX, scale * fY, scale * fZ); return p; } /** Scale the point's coordinates by scale. */ void scale(SkScalar value) { fX *= value; fY *= value; fZ *= value; } /** Return a new point whose X, Y and Z coordinates are the negative of the original point's */ SkPoint3 operator-() const { SkPoint3 neg; neg.fX = -fX; neg.fY = -fY; neg.fZ = -fZ; return neg; } /** Returns a new point whose coordinates are the difference between a and b (i.e., a - b) */ friend SkPoint3 operator-(const SkPoint3& a, const SkPoint3& b) { return { a.fX - b.fX, a.fY - b.fY, a.fZ - b.fZ }; } /** Returns a new point whose coordinates are the sum of a and b (a + b) */ friend SkPoint3 operator+(const SkPoint3& a, const SkPoint3& b) { return { a.fX + b.fX, a.fY + b.fY, a.fZ + b.fZ }; } /** Add v's coordinates to the point's */ void operator+=(const SkPoint3& v) { fX += v.fX; fY += v.fY; fZ += v.fZ; } /** Subtract v's coordinates from the point's */ void operator-=(const SkPoint3& v) { fX -= v.fX; fY -= v.fY; fZ -= v.fZ; } friend SkPoint3 operator*(SkScalar t, SkPoint3 p) { return { t * p.fX, t * p.fY, t * p.fZ }; } /** Returns true if fX, fY, and fZ are measurable values. @return true for values other than infinities and NaN */ bool isFinite() const { SkScalar accum = 0; accum *= fX; accum *= fY; accum *= fZ; // accum is either NaN or it is finite (zero). SkASSERT(0 == accum || SkScalarIsNaN(accum)); // value==value will be true iff value is not NaN // TODO: is it faster to say !accum or accum==accum? return !SkScalarIsNaN(accum); } /** Returns the dot product of a and b, treating them as 3D vectors */ static SkScalar DotProduct(const SkPoint3& a, const SkPoint3& b) { return a.fX * b.fX + a.fY * b.fY + a.fZ * b.fZ; } SkScalar dot(const SkPoint3& vec) const { return DotProduct(*this, vec); } /** Returns the cross product of a and b, treating them as 3D vectors */ static SkPoint3 CrossProduct(const SkPoint3& a, const SkPoint3& b) { SkPoint3 result; result.fX = a.fY*b.fZ - a.fZ*b.fY; result.fY = a.fZ*b.fX - a.fX*b.fZ; result.fZ = a.fX*b.fY - a.fY*b.fX; return result; } SkPoint3 cross(const SkPoint3& vec) const { return CrossProduct(*this, vec); } }; typedef SkPoint3 SkVector3; typedef SkPoint3 SkColor3f; class SkPath; /** \class SkRegion SkRegion describes the set of pixels used to clip SkCanvas. SkRegion is compact, efficiently storing a single integer rectangle, or a run length encoded array of rectangles. SkRegion may reduce the current SkCanvas clip, or may be drawn as one or more integer rectangles. SkRegion iterator returns the scan lines or rectangles contained by it, optionally intersecting a bounding rectangle. */ class SK_API SkRegion { typedef int32_t RunType; public: /** Constructs an empty SkRegion. SkRegion is set to empty bounds at (0, 0) with zero width and height. @return empty SkRegion example: https://fiddle.skia.org/c/@Region_empty_constructor */ SkRegion(); /** Constructs a copy of an existing region. Copy constructor makes two regions identical by value. Internally, region and the returned result share pointer values. The underlying SkRect array is copied when modified. Creating a SkRegion copy is very efficient and never allocates memory. SkRegion are always copied by value from the interface; the underlying shared pointers are not exposed. @param region SkRegion to copy by value @return copy of SkRegion example: https://fiddle.skia.org/c/@Region_copy_const_SkRegion */ SkRegion(const SkRegion& region); /** Constructs a rectangular SkRegion matching the bounds of rect. @param rect bounds of constructed SkRegion @return rectangular SkRegion example: https://fiddle.skia.org/c/@Region_copy_const_SkIRect */ explicit SkRegion(const SkIRect& rect); /** Releases ownership of any shared data and deletes data if SkRegion is sole owner. example: https://fiddle.skia.org/c/@Region_destructor */ ~SkRegion(); /** Constructs a copy of an existing region. Makes two regions identical by value. Internally, region and the returned result share pointer values. The underlying SkRect array is copied when modified. Creating a SkRegion copy is very efficient and never allocates memory. SkRegion are always copied by value from the interface; the underlying shared pointers are not exposed. @param region SkRegion to copy by value @return SkRegion to copy by value example: https://fiddle.skia.org/c/@Region_copy_operator */ SkRegion& operator=(const SkRegion& region); /** Compares SkRegion and other; returns true if they enclose exactly the same area. @param other SkRegion to compare @return true if SkRegion pair are equivalent example: https://fiddle.skia.org/c/@Region_equal1_operator */ bool operator==(const SkRegion& other) const; /** Compares SkRegion and other; returns true if they do not enclose the same area. @param other SkRegion to compare @return true if SkRegion pair are not equivalent */ bool operator!=(const SkRegion& other) const { return !(*this == other); } /** Sets SkRegion to src, and returns true if src bounds is not empty. This makes SkRegion and src identical by value. Internally, SkRegion and src share pointer values. The underlying SkRect array is copied when modified. Creating a SkRegion copy is very efficient and never allocates memory. SkRegion are always copied by value from the interface; the underlying shared pointers are not exposed. @param src SkRegion to copy @return copy of src */ bool set(const SkRegion& src) { *this = src; return !this->isEmpty(); } /** Exchanges SkIRect array of SkRegion and other. swap() internally exchanges pointers, so it is lightweight and does not allocate memory. swap() usage has largely been replaced by operator=(const SkRegion& region). SkPath do not copy their content on assignment until they are written to, making assignment as efficient as swap(). @param other operator=(const SkRegion& region) set example: https://fiddle.skia.org/c/@Region_swap */ void swap(SkRegion& other); /** Returns true if SkRegion is empty. Empty SkRegion has bounds width or height less than or equal to zero. SkRegion() constructs empty SkRegion; setEmpty() and setRect() with dimensionless data make SkRegion empty. @return true if bounds has no width or height */ bool isEmpty() const { return fRunHead == emptyRunHeadPtr(); } /** Returns true if SkRegion is one SkIRect with positive dimensions. @return true if SkRegion contains one SkIRect */ bool isRect() const { return fRunHead == kRectRunHeadPtr; } /** Returns true if SkRegion is described by more than one rectangle. @return true if SkRegion contains more than one SkIRect */ bool isComplex() const { return !this->isEmpty() && !this->isRect(); } /** Returns minimum and maximum axes values of SkIRect array. Returns (0, 0, 0, 0) if SkRegion is empty. @return combined bounds of all SkIRect elements */ const SkIRect& getBounds() const { return fBounds; } /** Returns a value that increases with the number of elements in SkRegion. Returns zero if SkRegion is empty. Returns one if SkRegion equals SkIRect; otherwise, returns value greater than one indicating that SkRegion is complex. Call to compare SkRegion for relative complexity. @return relative complexity example: https://fiddle.skia.org/c/@Region_computeRegionComplexity */ int computeRegionComplexity() const; /** Appends outline of SkRegion to path. Returns true if SkRegion is not empty; otherwise, returns false, and leaves path unmodified. @param path SkPath to append to @return true if path changed example: https://fiddle.skia.org/c/@Region_getBoundaryPath */ bool getBoundaryPath(SkPath* path) const; /** Constructs an empty SkRegion. SkRegion is set to empty bounds at (0, 0) with zero width and height. Always returns false. @return false example: https://fiddle.skia.org/c/@Region_setEmpty */ bool setEmpty(); /** Constructs a rectangular SkRegion matching the bounds of rect. If rect is empty, constructs empty and returns false. @param rect bounds of constructed SkRegion @return true if rect is not empty example: https://fiddle.skia.org/c/@Region_setRect */ bool setRect(const SkIRect& rect); /** Constructs SkRegion as the union of SkIRect in rects array. If count is zero, constructs empty SkRegion. Returns false if constructed SkRegion is empty. May be faster than repeated calls to op(). @param rects array of SkIRect @param count array size @return true if constructed SkRegion is not empty example: https://fiddle.skia.org/c/@Region_setRects */ bool setRects(const SkIRect rects[], int count); /** Constructs a copy of an existing region. Makes two regions identical by value. Internally, region and the returned result share pointer values. The underlying SkRect array is copied when modified. Creating a SkRegion copy is very efficient and never allocates memory. SkRegion are always copied by value from the interface; the underlying shared pointers are not exposed. @param region SkRegion to copy by value @return SkRegion to copy by value example: https://fiddle.skia.org/c/@Region_setRegion */ bool setRegion(const SkRegion& region); /** Constructs SkRegion to match outline of path within clip. Returns false if constructed SkRegion is empty. Constructed SkRegion draws the same pixels as path through clip when anti-aliasing is disabled. @param path SkPath providing outline @param clip SkRegion containing path @return true if constructed SkRegion is not empty example: https://fiddle.skia.org/c/@Region_setPath */ bool setPath(const SkPath& path, const SkRegion& clip); /** Returns true if SkRegion intersects rect. Returns false if either rect or SkRegion is empty, or do not intersect. @param rect SkIRect to intersect @return true if rect and SkRegion have area in common example: https://fiddle.skia.org/c/@Region_intersects */ bool intersects(const SkIRect& rect) const; /** Returns true if SkRegion intersects other. Returns false if either other or SkRegion is empty, or do not intersect. @param other SkRegion to intersect @return true if other and SkRegion have area in common example: https://fiddle.skia.org/c/@Region_intersects_2 */ bool intersects(const SkRegion& other) const; /** Returns true if SkIPoint (x, y) is inside SkRegion. Returns false if SkRegion is empty. @param x test SkIPoint x-coordinate @param y test SkIPoint y-coordinate @return true if (x, y) is inside SkRegion example: https://fiddle.skia.org/c/@Region_contains */ bool contains(int32_t x, int32_t y) const; /** Returns true if other is completely inside SkRegion. Returns false if SkRegion or other is empty. @param other SkIRect to contain @return true if other is inside SkRegion example: https://fiddle.skia.org/c/@Region_contains_2 */ bool contains(const SkIRect& other) const; /** Returns true if other is completely inside SkRegion. Returns false if SkRegion or other is empty. @param other SkRegion to contain @return true if other is inside SkRegion example: https://fiddle.skia.org/c/@Region_contains_3 */ bool contains(const SkRegion& other) const; /** Returns true if SkRegion is a single rectangle and contains r. May return false even though SkRegion contains r. @param r SkIRect to contain @return true quickly if r points are equal or inside */ bool quickContains(const SkIRect& r) const { SkASSERT(this->isEmpty() == fBounds.isEmpty()); // valid region return r.fLeft < r.fRight && r.fTop < r.fBottom && fRunHead == kRectRunHeadPtr && // this->isRect() /* fBounds.contains(left, top, right, bottom); */ fBounds.fLeft <= r.fLeft && fBounds.fTop <= r.fTop && fBounds.fRight >= r.fRight && fBounds.fBottom >= r.fBottom; } /** Returns true if SkRegion does not intersect rect. Returns true if rect is empty or SkRegion is empty. May return false even though SkRegion does not intersect rect. @param rect SkIRect to intersect @return true if rect does not intersect */ bool quickReject(const SkIRect& rect) const { return this->isEmpty() || rect.isEmpty() || !SkIRect::Intersects(fBounds, rect); } /** Returns true if SkRegion does not intersect rgn. Returns true if rgn is empty or SkRegion is empty. May return false even though SkRegion does not intersect rgn. @param rgn SkRegion to intersect @return true if rgn does not intersect */ bool quickReject(const SkRegion& rgn) const { return this->isEmpty() || rgn.isEmpty() || !SkIRect::Intersects(fBounds, rgn.fBounds); } /** Offsets SkRegion by ivector (dx, dy). Has no effect if SkRegion is empty. @param dx x-axis offset @param dy y-axis offset */ void translate(int dx, int dy) { this->translate(dx, dy, this); } /** Offsets SkRegion by ivector (dx, dy), writing result to dst. SkRegion may be passed as dst parameter, translating SkRegion in place. Has no effect if dst is nullptr. If SkRegion is empty, sets dst to empty. @param dx x-axis offset @param dy y-axis offset @param dst translated result example: https://fiddle.skia.org/c/@Region_translate_2 */ void translate(int dx, int dy, SkRegion* dst) const; /** \enum SkRegion::Op The logical operations that can be performed when combining two SkRegion. */ enum Op { kDifference_Op, //!< target minus operand kIntersect_Op, //!< target intersected with operand kUnion_Op, //!< target unioned with operand kXOR_Op, //!< target exclusive or with operand kReverseDifference_Op, //!< operand minus target kReplace_Op, //!< replace target with operand kLastOp = kReplace_Op, //!< last operator }; static const int kOpCnt = kLastOp + 1; /** Replaces SkRegion with the result of SkRegion op rect. Returns true if replaced SkRegion is not empty. @param rect SkIRect operand @return false if result is empty */ bool op(const SkIRect& rect, Op op) { if (this->isRect() && kIntersect_Op == op) { if (!fBounds.intersect(rect)) { return this->setEmpty(); } return true; } return this->op(*this, rect, op); } /** Replaces SkRegion with the result of SkRegion op rgn. Returns true if replaced SkRegion is not empty. @param rgn SkRegion operand @return false if result is empty */ bool op(const SkRegion& rgn, Op op) { return this->op(*this, rgn, op); } /** Replaces SkRegion with the result of rect op rgn. Returns true if replaced SkRegion is not empty. @param rect SkIRect operand @param rgn SkRegion operand @return false if result is empty example: https://fiddle.skia.org/c/@Region_op_4 */ bool op(const SkIRect& rect, const SkRegion& rgn, Op op); /** Replaces SkRegion with the result of rgn op rect. Returns true if replaced SkRegion is not empty. @param rgn SkRegion operand @param rect SkIRect operand @return false if result is empty example: https://fiddle.skia.org/c/@Region_op_5 */ bool op(const SkRegion& rgn, const SkIRect& rect, Op op); /** Replaces SkRegion with the result of rgna op rgnb. Returns true if replaced SkRegion is not empty. @param rgna SkRegion operand @param rgnb SkRegion operand @return false if result is empty example: https://fiddle.skia.org/c/@Region_op_6 */ bool op(const SkRegion& rgna, const SkRegion& rgnb, Op op); #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK /** Private. Android framework only. @return string representation of SkRegion */ char* toString(); #endif /** \class SkRegion::Iterator Returns sequence of rectangles, sorted along y-axis, then x-axis, that make up SkRegion. */ class SK_API Iterator { public: /** Initializes SkRegion::Iterator with an empty SkRegion. done() on SkRegion::Iterator returns true. Call reset() to initialized SkRegion::Iterator at a later time. @return empty SkRegion iterator */ Iterator() : fRgn(nullptr), fDone(true) {} /** Sets SkRegion::Iterator to return elements of SkIRect array in region. @param region SkRegion to iterate @return SkRegion iterator example: https://fiddle.skia.org/c/@Region_Iterator_copy_const_SkRegion */ Iterator(const SkRegion& region); /** SkPoint SkRegion::Iterator to start of SkRegion. Returns true if SkRegion was set; otherwise, returns false. @return true if SkRegion was set example: https://fiddle.skia.org/c/@Region_Iterator_rewind */ bool rewind(); /** Resets iterator, using the new SkRegion. @param region SkRegion to iterate example: https://fiddle.skia.org/c/@Region_Iterator_reset */ void reset(const SkRegion& region); /** Returns true if SkRegion::Iterator is pointing to final SkIRect in SkRegion. @return true if data parsing is complete */ bool done() const { return fDone; } /** Advances SkRegion::Iterator to next SkIRect in SkRegion if it is not done. example: https://fiddle.skia.org/c/@Region_Iterator_next */ void next(); /** Returns SkIRect element in SkRegion. Does not return predictable results if SkRegion is empty. @return part of SkRegion as SkIRect */ const SkIRect& rect() const { return fRect; } /** Returns SkRegion if set; otherwise, returns nullptr. @return iterated SkRegion */ const SkRegion* rgn() const { return fRgn; } private: const SkRegion* fRgn; const SkRegion::RunType* fRuns; SkIRect fRect = {0, 0, 0, 0}; bool fDone; }; /** \class SkRegion::Cliperator Returns the sequence of rectangles, sorted along y-axis, then x-axis, that make up SkRegion intersected with the specified clip rectangle. */ class SK_API Cliperator { public: /** Sets SkRegion::Cliperator to return elements of SkIRect array in SkRegion within clip. @param region SkRegion to iterate @param clip bounds of iteration @return SkRegion iterator example: https://fiddle.skia.org/c/@Region_Cliperator_const_SkRegion_const_SkIRect */ Cliperator(const SkRegion& region, const SkIRect& clip); /** Returns true if SkRegion::Cliperator is pointing to final SkIRect in SkRegion. @return true if data parsing is complete */ bool done() { return fDone; } /** Advances iterator to next SkIRect in SkRegion contained by clip. example: https://fiddle.skia.org/c/@Region_Cliperator_next */ void next(); /** Returns SkIRect element in SkRegion, intersected with clip passed to SkRegion::Cliperator constructor. Does not return predictable results if SkRegion is empty. @return part of SkRegion inside clip as SkIRect */ const SkIRect& rect() const { return fRect; } private: Iterator fIter; SkIRect fClip; SkIRect fRect = {0, 0, 0, 0}; bool fDone; }; /** \class SkRegion::Spanerator Returns the line segment ends within SkRegion that intersect a horizontal line. */ class Spanerator { public: /** Sets SkRegion::Spanerator to return line segments in SkRegion on scan line. @param region SkRegion to iterate @param y horizontal line to intersect @param left bounds of iteration @param right bounds of iteration @return SkRegion iterator example: https://fiddle.skia.org/c/@Region_Spanerator_const_SkRegion_int_int_int */ Spanerator(const SkRegion& region, int y, int left, int right); /** Advances iterator to next span intersecting SkRegion within line segment provided in constructor. Returns true if interval was found. @param left pointer to span start; may be nullptr @param right pointer to span end; may be nullptr @return true if interval was found example: https://fiddle.skia.org/c/@Region_Spanerator_next */ bool next(int* left, int* right); private: const SkRegion::RunType* fRuns; int fLeft, fRight; bool fDone; }; /** Writes SkRegion to buffer, and returns number of bytes written. If buffer is nullptr, returns number number of bytes that would be written. @param buffer storage for binary data @return size of SkRegion example: https://fiddle.skia.org/c/@Region_writeToMemory */ size_t writeToMemory(void* buffer) const; /** Constructs SkRegion from buffer of size length. Returns bytes read. Returned value will be multiple of four or zero if length was too small. @param buffer storage for binary data @param length size of buffer @return bytes read example: https://fiddle.skia.org/c/@Region_readFromMemory */ size_t readFromMemory(const void* buffer, size_t length); using sk_is_trivially_relocatable = std::true_type; private: static constexpr int kOpCount = kReplace_Op + 1; // T // [B N L R S] // S static constexpr int kRectRegionRuns = 7; struct RunHead; static RunHead* emptyRunHeadPtr() { return (SkRegion::RunHead*) -1; } static constexpr RunHead* kRectRunHeadPtr = nullptr; // allocate space for count runs void allocateRuns(int count); void allocateRuns(int count, int ySpanCount, int intervalCount); void allocateRuns(const RunHead& src); SkDEBUGCODE(void dump() const;) SkIRect fBounds; RunHead* fRunHead; static_assert(::sk_is_trivially_relocatable::value); static_assert(::sk_is_trivially_relocatable::value); void freeRuns(); /** * Return the runs from this region, consing up fake runs if the region * is empty or a rect. In those 2 cases, we use tmpStorage to hold the * run data. */ const RunType* getRuns(RunType tmpStorage[], int* intervals) const; // This is called with runs[] that do not yet have their interval-count // field set on each scanline. That is computed as part of this call // (inside ComputeRunBounds). bool setRuns(RunType runs[], int count); int count_runtype_values(int* itop, int* ibot) const; bool isValid() const; static void BuildRectRuns(const SkIRect& bounds, RunType runs[kRectRegionRuns]); // If the runs define a simple rect, return true and set bounds to that // rect. If not, return false and ignore bounds. static bool RunsAreARect(const SkRegion::RunType runs[], int count, SkIRect* bounds); /** * If the last arg is null, just return if the result is non-empty, * else store the result in the last arg. */ static bool Oper(const SkRegion&, const SkRegion&, SkRegion::Op, SkRegion*); friend struct RunHead; friend class Iterator; friend class Spanerator; friend class SkRegionPriv; friend class SkRgnBuilder; friend class SkFlatRegion; }; class SkMatrix; class SkString; /** \class SkRRect SkRRect describes a rounded rectangle with a bounds and a pair of radii for each corner. The bounds and radii can be set so that SkRRect describes: a rectangle with sharp corners; a circle; an oval; or a rectangle with one or more rounded corners. SkRRect allows implementing CSS properties that describe rounded corners. SkRRect may have up to eight different radii, one for each axis on each of its four corners. SkRRect may modify the provided parameters when initializing bounds and radii. If either axis radii is zero or less: radii are stored as zero; corner is square. If corner curves overlap, radii are proportionally reduced to fit within bounds. */ class SK_API SkRRect { public: /** Initializes bounds at (0, 0), the origin, with zero width and height. Initializes corner radii to (0, 0), and sets type of kEmpty_Type. @return empty SkRRect */ SkRRect() = default; /** Initializes to copy of rrect bounds and corner radii. @param rrect bounds and corner to copy @return copy of rrect */ SkRRect(const SkRRect& rrect) = default; /** Copies rrect bounds and corner radii. @param rrect bounds and corner to copy @return copy of rrect */ SkRRect& operator=(const SkRRect& rrect) = default; /** \enum SkRRect::Type Type describes possible specializations of SkRRect. Each Type is exclusive; a SkRRect may only have one type. Type members become progressively less restrictive; larger values of Type have more degrees of freedom than smaller values. */ enum Type { kEmpty_Type, //!< zero width or height kRect_Type, //!< non-zero width and height, and zeroed radii kOval_Type, //!< non-zero width and height filled with radii kSimple_Type, //!< non-zero width and height with equal radii kNinePatch_Type, //!< non-zero width and height with axis-aligned radii kComplex_Type, //!< non-zero width and height with arbitrary radii kLastType = kComplex_Type, //!< largest Type value }; Type getType() const { SkASSERT(this->isValid()); return static_cast(fType); } Type type() const { return this->getType(); } inline bool isEmpty() const { return kEmpty_Type == this->getType(); } inline bool isRect() const { return kRect_Type == this->getType(); } inline bool isOval() const { return kOval_Type == this->getType(); } inline bool isSimple() const { return kSimple_Type == this->getType(); } inline bool isNinePatch() const { return kNinePatch_Type == this->getType(); } inline bool isComplex() const { return kComplex_Type == this->getType(); } /** Returns span on the x-axis. This does not check if result fits in 32-bit float; result may be infinity. @return rect().fRight minus rect().fLeft */ SkScalar width() const { return fRect.width(); } /** Returns span on the y-axis. This does not check if result fits in 32-bit float; result may be infinity. @return rect().fBottom minus rect().fTop */ SkScalar height() const { return fRect.height(); } /** Returns top-left corner radii. If type() returns kEmpty_Type, kRect_Type, kOval_Type, or kSimple_Type, returns a value representative of all corner radii. If type() returns kNinePatch_Type or kComplex_Type, at least one of the remaining three corners has a different value. @return corner radii for simple types */ SkVector getSimpleRadii() const { return fRadii[0]; } /** Sets bounds to zero width and height at (0, 0), the origin. Sets corner radii to zero and sets type to kEmpty_Type. */ void setEmpty() { *this = SkRRect(); } /** Sets bounds to sorted rect, and sets corner radii to zero. If set bounds has width and height, and sets type to kRect_Type; otherwise, sets type to kEmpty_Type. @param rect bounds to set */ void setRect(const SkRect& rect) { if (!this->initializeRect(rect)) { return; } memset(fRadii, 0, sizeof(fRadii)); fType = kRect_Type; SkASSERT(this->isValid()); } /** Initializes bounds at (0, 0), the origin, with zero width and height. Initializes corner radii to (0, 0), and sets type of kEmpty_Type. @return empty SkRRect */ static SkRRect MakeEmpty() { return SkRRect(); } /** Initializes to copy of r bounds and zeroes corner radii. @param r bounds to copy @return copy of r */ static SkRRect MakeRect(const SkRect& r) { SkRRect rr; rr.setRect(r); return rr; } /** Sets bounds to oval, x-axis radii to half oval.width(), and all y-axis radii to half oval.height(). If oval bounds is empty, sets to kEmpty_Type. Otherwise, sets to kOval_Type. @param oval bounds of oval @return oval */ static SkRRect MakeOval(const SkRect& oval) { SkRRect rr; rr.setOval(oval); return rr; } /** Sets to rounded rectangle with the same radii for all four corners. If rect is empty, sets to kEmpty_Type. Otherwise, if xRad and yRad are zero, sets to kRect_Type. Otherwise, if xRad is at least half rect.width() and yRad is at least half rect.height(), sets to kOval_Type. Otherwise, sets to kSimple_Type. @param rect bounds of rounded rectangle @param xRad x-axis radius of corners @param yRad y-axis radius of corners @return rounded rectangle */ static SkRRect MakeRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { SkRRect rr; rr.setRectXY(rect, xRad, yRad); return rr; } /** Sets bounds to oval, x-axis radii to half oval.width(), and all y-axis radii to half oval.height(). If oval bounds is empty, sets to kEmpty_Type. Otherwise, sets to kOval_Type. @param oval bounds of oval */ void setOval(const SkRect& oval); /** Sets to rounded rectangle with the same radii for all four corners. If rect is empty, sets to kEmpty_Type. Otherwise, if xRad or yRad is zero, sets to kRect_Type. Otherwise, if xRad is at least half rect.width() and yRad is at least half rect.height(), sets to kOval_Type. Otherwise, sets to kSimple_Type. @param rect bounds of rounded rectangle @param xRad x-axis radius of corners @param yRad y-axis radius of corners example: https://fiddle.skia.org/c/@RRect_setRectXY */ void setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad); /** Sets bounds to rect. Sets radii to (leftRad, topRad), (rightRad, topRad), (rightRad, bottomRad), (leftRad, bottomRad). If rect is empty, sets to kEmpty_Type. Otherwise, if leftRad and rightRad are zero, sets to kRect_Type. Otherwise, if topRad and bottomRad are zero, sets to kRect_Type. Otherwise, if leftRad and rightRad are equal and at least half rect.width(), and topRad and bottomRad are equal at least half rect.height(), sets to kOval_Type. Otherwise, if leftRad and rightRad are equal, and topRad and bottomRad are equal, sets to kSimple_Type. Otherwise, sets to kNinePatch_Type. Nine patch refers to the nine parts defined by the radii: one center rectangle, four edge patches, and four corner patches. @param rect bounds of rounded rectangle @param leftRad left-top and left-bottom x-axis radius @param topRad left-top and right-top y-axis radius @param rightRad right-top and right-bottom x-axis radius @param bottomRad left-bottom and right-bottom y-axis radius */ void setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, SkScalar rightRad, SkScalar bottomRad); /** Sets bounds to rect. Sets radii array for individual control of all for corners. If rect is empty, sets to kEmpty_Type. Otherwise, if one of each corner radii are zero, sets to kRect_Type. Otherwise, if all x-axis radii are equal and at least half rect.width(), and all y-axis radii are equal at least half rect.height(), sets to kOval_Type. Otherwise, if all x-axis radii are equal, and all y-axis radii are equal, sets to kSimple_Type. Otherwise, sets to kNinePatch_Type. @param rect bounds of rounded rectangle @param radii corner x-axis and y-axis radii example: https://fiddle.skia.org/c/@RRect_setRectRadii */ void setRectRadii(const SkRect& rect, const SkVector radii[4]); /** \enum SkRRect::Corner The radii are stored: top-left, top-right, bottom-right, bottom-left. */ enum Corner { kUpperLeft_Corner, //!< index of top-left corner radii kUpperRight_Corner, //!< index of top-right corner radii kLowerRight_Corner, //!< index of bottom-right corner radii kLowerLeft_Corner, //!< index of bottom-left corner radii }; /** Returns bounds. Bounds may have zero width or zero height. Bounds right is greater than or equal to left; bounds bottom is greater than or equal to top. Result is identical to getBounds(). @return bounding box */ const SkRect& rect() const { return fRect; } /** Returns scalar pair for radius of curve on x-axis and y-axis for one corner. Both radii may be zero. If not zero, both are positive and finite. @return x-axis and y-axis radii for one corner */ SkVector radii(Corner corner) const { return fRadii[corner]; } /** Returns bounds. Bounds may have zero width or zero height. Bounds right is greater than or equal to left; bounds bottom is greater than or equal to top. Result is identical to rect(). @return bounding box */ const SkRect& getBounds() const { return fRect; } /** Returns true if bounds and radii in a are equal to bounds and radii in b. a and b are not equal if either contain NaN. a and b are equal if members contain zeroes with different signs. @param a SkRect bounds and radii to compare @param b SkRect bounds and radii to compare @return true if members are equal */ friend bool operator==(const SkRRect& a, const SkRRect& b) { return a.fRect == b.fRect && SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8); } /** Returns true if bounds and radii in a are not equal to bounds and radii in b. a and b are not equal if either contain NaN. a and b are equal if members contain zeroes with different signs. @param a SkRect bounds and radii to compare @param b SkRect bounds and radii to compare @return true if members are not equal */ friend bool operator!=(const SkRRect& a, const SkRRect& b) { return a.fRect != b.fRect || !SkScalarsEqual(&a.fRadii[0].fX, &b.fRadii[0].fX, 8); } /** Copies SkRRect to dst, then insets dst bounds by dx and dy, and adjusts dst radii by dx and dy. dx and dy may be positive, negative, or zero. dst may be SkRRect. If either corner radius is zero, the corner has no curvature and is unchanged. Otherwise, if adjusted radius becomes negative, pins radius to zero. If dx exceeds half dst bounds width, dst bounds left and right are set to bounds x-axis center. If dy exceeds half dst bounds height, dst bounds top and bottom are set to bounds y-axis center. If dx or dy cause the bounds to become infinite, dst bounds is zeroed. @param dx added to rect().fLeft, and subtracted from rect().fRight @param dy added to rect().fTop, and subtracted from rect().fBottom @param dst insets bounds and radii example: https://fiddle.skia.org/c/@RRect_inset */ void inset(SkScalar dx, SkScalar dy, SkRRect* dst) const; /** Insets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be positive, negative, or zero. If either corner radius is zero, the corner has no curvature and is unchanged. Otherwise, if adjusted radius becomes negative, pins radius to zero. If dx exceeds half bounds width, bounds left and right are set to bounds x-axis center. If dy exceeds half bounds height, bounds top and bottom are set to bounds y-axis center. If dx or dy cause the bounds to become infinite, bounds is zeroed. @param dx added to rect().fLeft, and subtracted from rect().fRight @param dy added to rect().fTop, and subtracted from rect().fBottom */ void inset(SkScalar dx, SkScalar dy) { this->inset(dx, dy, this); } /** Outsets dst bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be positive, negative, or zero. If either corner radius is zero, the corner has no curvature and is unchanged. Otherwise, if adjusted radius becomes negative, pins radius to zero. If dx exceeds half dst bounds width, dst bounds left and right are set to bounds x-axis center. If dy exceeds half dst bounds height, dst bounds top and bottom are set to bounds y-axis center. If dx or dy cause the bounds to become infinite, dst bounds is zeroed. @param dx subtracted from rect().fLeft, and added to rect().fRight @param dy subtracted from rect().fTop, and added to rect().fBottom @param dst outset bounds and radii */ void outset(SkScalar dx, SkScalar dy, SkRRect* dst) const { this->inset(-dx, -dy, dst); } /** Outsets bounds by dx and dy, and adjusts radii by dx and dy. dx and dy may be positive, negative, or zero. If either corner radius is zero, the corner has no curvature and is unchanged. Otherwise, if adjusted radius becomes negative, pins radius to zero. If dx exceeds half bounds width, bounds left and right are set to bounds x-axis center. If dy exceeds half bounds height, bounds top and bottom are set to bounds y-axis center. If dx or dy cause the bounds to become infinite, bounds is zeroed. @param dx subtracted from rect().fLeft, and added to rect().fRight @param dy subtracted from rect().fTop, and added to rect().fBottom */ void outset(SkScalar dx, SkScalar dy) { this->inset(-dx, -dy, this); } /** Translates SkRRect by (dx, dy). @param dx offset added to rect().fLeft and rect().fRight @param dy offset added to rect().fTop and rect().fBottom */ void offset(SkScalar dx, SkScalar dy) { fRect.offset(dx, dy); } /** Returns SkRRect translated by (dx, dy). @param dx offset added to rect().fLeft and rect().fRight @param dy offset added to rect().fTop and rect().fBottom @return SkRRect bounds offset by (dx, dy), with unchanged corner radii */ [[nodiscard]] SkRRect makeOffset(SkScalar dx, SkScalar dy) const { return SkRRect(fRect.makeOffset(dx, dy), fRadii, fType); } /** Returns true if rect is inside the bounds and corner radii, and if SkRRect and rect are not empty. @param rect area tested for containment @return true if SkRRect contains rect example: https://fiddle.skia.org/c/@RRect_contains */ bool contains(const SkRect& rect) const; /** Returns true if bounds and radii values are finite and describe a SkRRect SkRRect::Type that matches getType(). All SkRRect methods construct valid types, even if the input values are not valid. Invalid SkRRect data can only be generated by corrupting memory. @return true if bounds and radii match type() example: https://fiddle.skia.org/c/@RRect_isValid */ bool isValid() const; static constexpr size_t kSizeInMemory = 12 * sizeof(SkScalar); /** Writes SkRRect to buffer. Writes kSizeInMemory bytes, and returns kSizeInMemory, the number of bytes written. @param buffer storage for SkRRect @return bytes written, kSizeInMemory example: https://fiddle.skia.org/c/@RRect_writeToMemory */ size_t writeToMemory(void* buffer) const; /** Reads SkRRect from buffer, reading kSizeInMemory bytes. Returns kSizeInMemory, bytes read if length is at least kSizeInMemory. Otherwise, returns zero. @param buffer memory to read from @param length size of buffer @return bytes read, or 0 if length is less than kSizeInMemory example: https://fiddle.skia.org/c/@RRect_readFromMemory */ size_t readFromMemory(const void* buffer, size_t length); /** Transforms by SkRRect by matrix, storing result in dst. Returns true if SkRRect transformed can be represented by another SkRRect. Returns false if matrix contains transformations that are not axis aligned. Asserts in debug builds if SkRRect equals dst. @param matrix SkMatrix specifying the transform @param dst SkRRect to store the result @return true if transformation succeeded. example: https://fiddle.skia.org/c/@RRect_transform */ bool transform(const SkMatrix& matrix, SkRRect* dst) const; /** Writes text representation of SkRRect to standard output. Set asHex true to generate exact binary representations of floating point numbers. @param asHex true if SkScalar values are written as hexadecimal example: https://fiddle.skia.org/c/@RRect_dump */ void dump(bool asHex) const; SkString dumpToString(bool asHex) const; /** Writes text representation of SkRRect to standard output. The representation may be directly compiled as C++ code. Floating point values are written with limited precision; it may not be possible to reconstruct original SkRRect from output. */ void dump() const { this->dump(false); } /** Writes text representation of SkRRect to standard output. The representation may be directly compiled as C++ code. Floating point values are written in hexadecimal to preserve their exact bit pattern. The output reconstructs the original SkRRect. */ void dumpHex() const { this->dump(true); } private: static bool AreRectAndRadiiValid(const SkRect&, const SkVector[4]); SkRRect(const SkRect& rect, const SkVector radii[4], int32_t type) : fRect(rect) , fRadii{radii[0], radii[1], radii[2], radii[3]} , fType(type) {} /** * Initializes fRect. If the passed in rect is not finite or empty the rrect will be fully * initialized and false is returned. Otherwise, just fRect is initialized and true is returned. */ bool initializeRect(const SkRect&); void computeType(); bool checkCornerContainment(SkScalar x, SkScalar y) const; // Returns true if the radii had to be scaled to fit rect bool scaleRadii(); SkRect fRect = SkRect::MakeEmpty(); // Radii order is UL, UR, LR, LL. Use Corner enum to index into fRadii[] SkVector fRadii[4] = {{0, 0}, {0, 0}, {0,0}, {0,0}}; // use an explicitly sized type so we're sure the class is dense (no uninitialized bytes) int32_t fType = kEmpty_Type; // TODO: add padding so we can use memcpy for flattening and not copy uninitialized data // to access fRadii directly friend class SkPath; friend class SkRRectPriv; }; /** * A compressed form of a rotation+scale matrix. * * [ fSCos -fSSin fTx ] * [ fSSin fSCos fTy ] * [ 0 0 1 ] */ struct SK_API SkRSXform { static SkRSXform Make(SkScalar scos, SkScalar ssin, SkScalar tx, SkScalar ty) { SkRSXform xform = { scos, ssin, tx, ty }; return xform; } /* * Initialize a new xform based on the scale, rotation (in radians), final tx,ty location * and anchor-point ax,ay within the src quad. * * Note: the anchor point is not normalized (e.g. 0...1) but is in pixels of the src image. */ static SkRSXform MakeFromRadians(SkScalar scale, SkScalar radians, SkScalar tx, SkScalar ty, SkScalar ax, SkScalar ay) { const SkScalar s = SkScalarSin(radians) * scale; const SkScalar c = SkScalarCos(radians) * scale; return Make(c, s, tx + -c * ax + s * ay, ty + -s * ax - c * ay); } SkScalar fSCos; SkScalar fSSin; SkScalar fTx; SkScalar fTy; bool rectStaysRect() const { return 0 == fSCos || 0 == fSSin; } void setIdentity() { fSCos = 1; fSSin = fTx = fTy = 0; } void set(SkScalar scos, SkScalar ssin, SkScalar tx, SkScalar ty) { fSCos = scos; fSSin = ssin; fTx = tx; fTy = ty; } void toQuad(SkScalar width, SkScalar height, SkPoint quad[4]) const; void toQuad(const SkSize& size, SkPoint quad[4]) const { this->toQuad(size.width(), size.height(), quad); } void toTriStrip(SkScalar width, SkScalar height, SkPoint strip[4]) const; }; class SkStreamAsset; /** * SkStream -- abstraction for a source of bytes. Subclasses can be backed by * memory, or a file, or something else. * * NOTE: * * Classic "streams" APIs are sort of async, in that on a request for N * bytes, they may return fewer than N bytes on a given call, in which case * the caller can "try again" to get more bytes, eventually (modulo an error) * receiving their total N bytes. * * Skia streams behave differently. They are effectively synchronous, and will * always return all N bytes of the request if possible. If they return fewer * (the read() call returns the number of bytes read) then that means there is * no more data (at EOF or hit an error). The caller should *not* call again * in hopes of fulfilling more of the request. */ class SK_API SkStream { public: virtual ~SkStream() {} SkStream() {} /** * Attempts to open the specified file as a stream, returns nullptr on failure. */ static std::unique_ptr MakeFromFile(const char path[]); /** Reads or skips size number of bytes. * If buffer == NULL, skip size bytes, return how many were skipped. * If buffer != NULL, copy size bytes into buffer, return how many were copied. * @param buffer when NULL skip size bytes, otherwise copy size bytes into buffer * @param size the number of bytes to skip or copy * @return the number of bytes actually read. */ virtual size_t read(void* buffer, size_t size) = 0; /** Skip size number of bytes. * @return the actual number bytes that could be skipped. */ size_t skip(size_t size) { return this->read(nullptr, size); } /** * Attempt to peek at size bytes. * If this stream supports peeking, copy min(size, peekable bytes) into * buffer, and return the number of bytes copied. * If the stream does not support peeking, or cannot peek any bytes, * return 0 and leave buffer unchanged. * The stream is guaranteed to be in the same visible state after this * call, regardless of success or failure. * @param buffer Must not be NULL, and must be at least size bytes. Destination * to copy bytes. * @param size Number of bytes to copy. * @return The number of bytes peeked/copied. */ virtual size_t peek(void* /*buffer*/, size_t /*size*/) const { return 0; } /** Returns true when all the bytes in the stream have been read. * As SkStream represents synchronous I/O, isAtEnd returns false when the * final stream length isn't known yet, even when all the bytes available * so far have been read. * This may return true early (when there are no more bytes to be read) * or late (after the first unsuccessful read). */ virtual bool isAtEnd() const = 0; [[nodiscard]] bool readS8(int8_t*); [[nodiscard]] bool readS16(int16_t*); [[nodiscard]] bool readS32(int32_t*); [[nodiscard]] bool readU8(uint8_t* i) { return this->readS8((int8_t*)i); } [[nodiscard]] bool readU16(uint16_t* i) { return this->readS16((int16_t*)i); } [[nodiscard]] bool readU32(uint32_t* i) { return this->readS32((int32_t*)i); } [[nodiscard]] bool readBool(bool* b) { uint8_t i; if (!this->readU8(&i)) { return false; } *b = (i != 0); return true; } [[nodiscard]] bool readScalar(SkScalar*); [[nodiscard]] bool readPackedUInt(size_t*); //SkStreamRewindable /** Rewinds to the beginning of the stream. Returns true if the stream is known * to be at the beginning after this call returns. */ virtual bool rewind() { return false; } /** Duplicates this stream. If this cannot be done, returns NULL. * The returned stream will be positioned at the beginning of its data. */ std::unique_ptr duplicate() const { return std::unique_ptr(this->onDuplicate()); } /** Duplicates this stream. If this cannot be done, returns NULL. * The returned stream will be positioned the same as this stream. */ std::unique_ptr fork() const { return std::unique_ptr(this->onFork()); } //SkStreamSeekable /** Returns true if this stream can report its current position. */ virtual bool hasPosition() const { return false; } /** Returns the current position in the stream. If this cannot be done, returns 0. */ virtual size_t getPosition() const { return 0; } /** Seeks to an absolute position in the stream. If this cannot be done, returns false. * If an attempt is made to seek past the end of the stream, the position will be set * to the end of the stream. */ virtual bool seek(size_t /*position*/) { return false; } /** Seeks to an relative offset in the stream. If this cannot be done, returns false. * If an attempt is made to move to a position outside the stream, the position will be set * to the closest point within the stream (beginning or end). */ virtual bool move(long /*offset*/) { return false; } //SkStreamAsset /** Returns true if this stream can report its total length. */ virtual bool hasLength() const { return false; } /** Returns the total length of the stream. If this cannot be done, returns 0. */ virtual size_t getLength() const { return 0; } //SkStreamMemory /** Returns the starting address for the data. If this cannot be done, returns NULL. */ //TODO: replace with virtual const SkData* getData() virtual const void* getMemoryBase() { return nullptr; } private: virtual SkStream* onDuplicate() const { return nullptr; } virtual SkStream* onFork() const { return nullptr; } SkStream(SkStream&&) = delete; SkStream(const SkStream&) = delete; SkStream& operator=(SkStream&&) = delete; SkStream& operator=(const SkStream&) = delete; }; /** SkStreamRewindable is a SkStream for which rewind and duplicate are required. */ class SK_API SkStreamRewindable : public SkStream { public: bool rewind() override = 0; std::unique_ptr duplicate() const { return std::unique_ptr(this->onDuplicate()); } private: SkStreamRewindable* onDuplicate() const override = 0; }; /** SkStreamSeekable is a SkStreamRewindable for which position, seek, move, and fork are required. */ class SK_API SkStreamSeekable : public SkStreamRewindable { public: std::unique_ptr duplicate() const { return std::unique_ptr(this->onDuplicate()); } bool hasPosition() const override { return true; } size_t getPosition() const override = 0; bool seek(size_t position) override = 0; bool move(long offset) override = 0; std::unique_ptr fork() const { return std::unique_ptr(this->onFork()); } private: SkStreamSeekable* onDuplicate() const override = 0; SkStreamSeekable* onFork() const override = 0; }; /** SkStreamAsset is a SkStreamSeekable for which getLength is required. */ class SK_API SkStreamAsset : public SkStreamSeekable { public: bool hasLength() const override { return true; } size_t getLength() const override = 0; std::unique_ptr duplicate() const { return std::unique_ptr(this->onDuplicate()); } std::unique_ptr fork() const { return std::unique_ptr(this->onFork()); } private: SkStreamAsset* onDuplicate() const override = 0; SkStreamAsset* onFork() const override = 0; }; /** SkStreamMemory is a SkStreamAsset for which getMemoryBase is required. */ class SK_API SkStreamMemory : public SkStreamAsset { public: const void* getMemoryBase() override = 0; std::unique_ptr duplicate() const { return std::unique_ptr(this->onDuplicate()); } std::unique_ptr fork() const { return std::unique_ptr(this->onFork()); } private: SkStreamMemory* onDuplicate() const override = 0; SkStreamMemory* onFork() const override = 0; }; class SK_API SkWStream { public: virtual ~SkWStream(); SkWStream() {} /** Called to write bytes to a SkWStream. Returns true on success @param buffer the address of at least size bytes to be written to the stream @param size The number of bytes in buffer to write to the stream @return true on success */ virtual bool write(const void* buffer, size_t size) = 0; virtual void flush(); virtual size_t bytesWritten() const = 0; // helpers bool write8(U8CPU value) { uint8_t v = SkToU8(value); return this->write(&v, 1); } bool write16(U16CPU value) { uint16_t v = SkToU16(value); return this->write(&v, 2); } bool write32(uint32_t v) { return this->write(&v, 4); } bool writeText(const char text[]) { SkASSERT(text); return this->write(text, std::strlen(text)); } bool newline() { return this->write("\n", std::strlen("\n")); } bool writeDecAsText(int32_t); bool writeBigDecAsText(int64_t, int minDigits = 0); bool writeHexAsText(uint32_t, int minDigits = 0); bool writeScalarAsText(SkScalar); bool writeBool(bool v) { return this->write8(v); } bool writeScalar(SkScalar); bool writePackedUInt(size_t); bool writeStream(SkStream* input, size_t length); /** * This returns the number of bytes in the stream required to store * 'value'. */ static int SizeOfPackedUInt(size_t value); private: SkWStream(const SkWStream&) = delete; SkWStream& operator=(const SkWStream&) = delete; }; class SK_API SkNullWStream : public SkWStream { public: SkNullWStream() : fBytesWritten(0) {} bool write(const void* , size_t n) override { fBytesWritten += n; return true; } void flush() override {} size_t bytesWritten() const override { return fBytesWritten; } private: size_t fBytesWritten; }; //////////////////////////////////////////////////////////////////////////////////////// /** A stream that wraps a C FILE* file stream. */ class SK_API SkFILEStream : public SkStreamAsset { public: /** Initialize the stream by calling sk_fopen on the specified path. * This internal stream will be closed in the destructor. */ explicit SkFILEStream(const char path[] = nullptr); /** Initialize the stream with an existing C FILE stream. * The current position of the C FILE stream will be considered the * beginning of the SkFILEStream and the current seek end of the FILE will be the end. * The C FILE stream will be closed in the destructor. */ explicit SkFILEStream(FILE* file); /** Initialize the stream with an existing C FILE stream. * The current position of the C FILE stream will be considered the * beginning of the SkFILEStream and size bytes later will be the end. * The C FILE stream will be closed in the destructor. */ explicit SkFILEStream(FILE* file, size_t size); ~SkFILEStream() override; static std::unique_ptr Make(const char path[]) { std::unique_ptr stream(new SkFILEStream(path)); return stream->isValid() ? std::move(stream) : nullptr; } /** Returns true if the current path could be opened. */ bool isValid() const { return fFILE != nullptr; } /** Close this SkFILEStream. */ void close(); size_t read(void* buffer, size_t size) override; bool isAtEnd() const override; bool rewind() override; std::unique_ptr duplicate() const { return std::unique_ptr(this->onDuplicate()); } size_t getPosition() const override; bool seek(size_t position) override; bool move(long offset) override; std::unique_ptr fork() const { return std::unique_ptr(this->onFork()); } size_t getLength() const override; private: explicit SkFILEStream(FILE*, size_t size, size_t start); explicit SkFILEStream(std::shared_ptr, size_t end, size_t start); explicit SkFILEStream(std::shared_ptr, size_t end, size_t start, size_t current); SkStreamAsset* onDuplicate() const override; SkStreamAsset* onFork() const override; std::shared_ptr fFILE; // My own council will I keep on sizes and offsets. // These are seek positions in the underling FILE, not offsets into the stream. size_t fEnd; size_t fStart; size_t fCurrent; using INHERITED = SkStreamAsset; }; class SK_API SkMemoryStream : public SkStreamMemory { public: SkMemoryStream(); /** We allocate (and free) the memory. Write to it via getMemoryBase() */ SkMemoryStream(size_t length); /** If copyData is true, the stream makes a private copy of the data. */ SkMemoryStream(const void* data, size_t length, bool copyData = false); /** Creates the stream to read from the specified data */ SkMemoryStream(sk_sp data); /** Returns a stream with a copy of the input data. */ static std::unique_ptr MakeCopy(const void* data, size_t length); /** Returns a stream with a bare pointer reference to the input data. */ static std::unique_ptr MakeDirect(const void* data, size_t length); /** Returns a stream with a shared reference to the input data. */ static std::unique_ptr Make(sk_sp data); /** Resets the stream to the specified data and length, just like the constructor. if copyData is true, the stream makes a private copy of the data */ virtual void setMemory(const void* data, size_t length, bool copyData = false); /** Replace any memory buffer with the specified buffer. The caller must have allocated data with sk_malloc or sk_realloc, since it will be freed with sk_free. */ void setMemoryOwned(const void* data, size_t length); sk_sp asData() const { return fData; } void setData(sk_sp data); void skipToAlign4(); const void* getAtPos(); size_t read(void* buffer, size_t size) override; bool isAtEnd() const override; size_t peek(void* buffer, size_t size) const override; bool rewind() override; std::unique_ptr duplicate() const { return std::unique_ptr(this->onDuplicate()); } size_t getPosition() const override; bool seek(size_t position) override; bool move(long offset) override; std::unique_ptr fork() const { return std::unique_ptr(this->onFork()); } size_t getLength() const override; const void* getMemoryBase() override; private: SkMemoryStream* onDuplicate() const override; SkMemoryStream* onFork() const override; sk_sp fData; size_t fOffset; using INHERITED = SkStreamMemory; }; ///////////////////////////////////////////////////////////////////////////////////////////// class SK_API SkFILEWStream : public SkWStream { public: SkFILEWStream(const char path[]); ~SkFILEWStream() override; /** Returns true if the current path could be opened. */ bool isValid() const { return fFILE != nullptr; } bool write(const void* buffer, size_t size) override; void flush() override; void fsync(); size_t bytesWritten() const override; private: FILE* fFILE; using INHERITED = SkWStream; }; class SK_API SkDynamicMemoryWStream : public SkWStream { public: SkDynamicMemoryWStream() = default; SkDynamicMemoryWStream(SkDynamicMemoryWStream&&); SkDynamicMemoryWStream& operator=(SkDynamicMemoryWStream&&); ~SkDynamicMemoryWStream() override; bool write(const void* buffer, size_t size) override; size_t bytesWritten() const override; bool read(void* buffer, size_t offset, size_t size); /** More efficient version of read(dst, 0, bytesWritten()). */ void copyTo(void* dst) const; bool writeToStream(SkWStream* dst) const; /** Equivalent to copyTo() followed by reset(), but may save memory use. */ void copyToAndReset(void* dst); /** Equivalent to writeToStream() followed by reset(), but may save memory use. */ bool writeToAndReset(SkWStream* dst); /** Equivalent to writeToStream() followed by reset(), but may save memory use. When the dst is also a SkDynamicMemoryWStream, the implementation is constant time. */ bool writeToAndReset(SkDynamicMemoryWStream* dst); /** Prepend this stream to dst, resetting this. */ void prependToAndReset(SkDynamicMemoryWStream* dst); /** Return the contents as SkData, and then reset the stream. */ sk_sp detachAsData(); /** Reset, returning a reader stream with the current content. */ std::unique_ptr detachAsStream(); /** Reset the stream to its original, empty, state. */ void reset(); void padToAlign4(); private: struct Block; Block* fHead = nullptr; Block* fTail = nullptr; size_t fBytesWrittenBeforeTail = 0; #ifdef SK_DEBUG void validate() const; #else void validate() const {} #endif // For access to the Block type. friend class SkBlockMemoryStream; friend class SkBlockMemoryRefCnt; using INHERITED = SkWStream; }; template static constexpr T SkAlign2(T x) { return (x + 1) >> 1 << 1; } template static constexpr T SkAlign4(T x) { return (x + 3) >> 2 << 2; } template static constexpr T SkAlign8(T x) { return (x + 7) >> 3 << 3; } template static constexpr bool SkIsAlign2(T x) { return 0 == (x & 1); } template static constexpr bool SkIsAlign4(T x) { return 0 == (x & 3); } template static constexpr bool SkIsAlign8(T x) { return 0 == (x & 7); } template static constexpr T SkAlignPtr(T x) { return sizeof(void*) == 8 ? SkAlign8(x) : SkAlign4(x); } template static constexpr bool SkIsAlignPtr(T x) { return sizeof(void*) == 8 ? SkIsAlign8(x) : SkIsAlign4(x); } /** * align up to a power of 2 */ static inline constexpr size_t SkAlignTo(size_t x, size_t alignment) { // The same as alignment && SkIsPow2(value), w/o a dependency cycle. SkASSERT(alignment && (alignment & (alignment - 1)) == 0); return (x + alignment - 1) & ~(alignment - 1); } /** \class SkNoncopyable (DEPRECATED) SkNoncopyable is the base class for objects that do not want to be copied. It hides its copy-constructor and its assignment-operator. */ class SK_API SkNoncopyable { public: SkNoncopyable() = default; SkNoncopyable(SkNoncopyable&&) = default; SkNoncopyable& operator =(SkNoncopyable&&) = default; private: SkNoncopyable(const SkNoncopyable&) = delete; SkNoncopyable& operator=(const SkNoncopyable&) = delete; }; // The sknonstd namespace contains things we would like to be proposed and feel std-ish. namespace sknonstd { // The name 'copy' here is fraught with peril. In this case it means 'append', not 'overwrite'. // Alternate proposed names are 'propagate', 'augment', or 'append' (and 'add', but already taken). // std::experimental::propagate_const already exists for other purposes in TSv2. // These also follow the pattern used by boost. template struct copy_const { using type = std::conditional_t::value, std::add_const_t, D>; }; template using copy_const_t = typename copy_const::type; template struct copy_volatile { using type = std::conditional_t::value, std::add_volatile_t, D>; }; template using copy_volatile_t = typename copy_volatile::type; template struct copy_cv { using type = copy_volatile_t, S>; }; template using copy_cv_t = typename copy_cv::type; // The name 'same' here means 'overwrite'. // Alternate proposed names are 'replace', 'transfer', or 'qualify_from'. // same_xxx can be written as copy_xxx, S> template using same_const = copy_const, S>; template using same_const_t = typename same_const::type; template using same_volatile =copy_volatile,S>; template using same_volatile_t = typename same_volatile::type; template using same_cv = copy_cv, S>; template using same_cv_t = typename same_cv::type; } // namespace sknonstd template constexpr int SkCount(const Container& c) { return SkTo(std::size(c)); } /** \file SkTemplates.h This file contains light-weight template classes for type-safe and exception-safe resource management. */ /** * Marks a local variable as known to be unused (to avoid warnings). * Note that this does *not* prevent the local variable from being optimized away. */ template inline void sk_ignore_unused_variable(const T&) { } /** * This is a general purpose absolute-value function. * See SkAbs32 in (SkSafe32.h) for a 32-bit int specific version that asserts. */ template static inline T SkTAbs(T value) { if (value < 0) { value = -value; } return value; } /** * Returns a pointer to a D which comes immediately after S[count]. */ template inline D* SkTAfter(S* ptr, size_t count = 1) { return reinterpret_cast(ptr + count); } /** * Returns a pointer to a D which comes byteOffset bytes after S. */ template inline D* SkTAddOffset(S* ptr, ptrdiff_t byteOffset) { // The intermediate char* has the same cv-ness as D as this produces better error messages. // This relies on the fact that reinterpret_cast can add constness, but cannot remove it. return reinterpret_cast(reinterpret_cast*>(ptr) + byteOffset); } template struct SkOverloadedFunctionObject { template auto operator()(Args&&... args) const -> decltype(P(std::forward(args)...)) { return P(std::forward(args)...); } }; template using SkFunctionObject = SkOverloadedFunctionObject, F>; /** \class SkAutoTCallVProc Call a function when this goes out of scope. The template uses two parameters, the object, and a function that is to be called in the destructor. If release() is called, the object reference is set to null. If the object reference is null when the destructor is called, we do not call the function. */ template class SkAutoTCallVProc : public std::unique_ptr> { using inherited = std::unique_ptr>; public: using inherited::inherited; SkAutoTCallVProc(const SkAutoTCallVProc&) = delete; SkAutoTCallVProc(SkAutoTCallVProc&& that) : inherited(std::move(that)) {} operator T*() const { return this->get(); } }; namespace skia_private { /** Allocate an array of T elements, and free the array in the destructor */ template class AutoTArray { public: AutoTArray() {} // Allocate size number of T elements explicit AutoTArray(size_t size) { fSize = check_size_bytes_too_big(size); fData.reset(size > 0 ? new T[size] : nullptr); } // TODO: remove when all uses are gone. explicit AutoTArray(int size) : AutoTArray(SkToSizeT(size)) {} AutoTArray(AutoTArray&& other) : fData(std::move(other.fData)) { fSize = std::exchange(other.fSize, 0); } AutoTArray& operator=(AutoTArray&& other) { if (this != &other) { fData = std::move(other.fData); fSize = std::exchange(other.fSize, 0); } return *this; } // Reallocates given a new count. Reallocation occurs even if new count equals old count. void reset(size_t count = 0) { *this = AutoTArray(count); } T* get() const { return fData.get(); } T& operator[](size_t index) const { return fData[sk_collection_check_bounds(index, fSize)]; } const T* data() const { return fData.get(); } T* data() { return fData.get(); } size_t size() const { return fSize; } bool empty() const { return fSize == 0; } size_t size_bytes() const { return sizeof(T) * fSize; } T* begin() { return fData; } const T* begin() const { return fData; } // It's safe to use fItemArray + fSize because if fItemArray is nullptr then adding 0 is // valid and returns nullptr. See [expr.add] in the C++ standard. T* end() { if (fData == nullptr) { SkASSERT(fSize == 0); } return fData + fSize; } const T* end() const { if (fData == nullptr) { SkASSERT(fSize == 0); } return fData + fSize; } private: std::unique_ptr fData; size_t fSize = 0; }; /** Wraps AutoTArray, with room for kCountRequested elements preallocated. */ template class AutoSTArray { public: AutoSTArray(AutoSTArray&&) = delete; AutoSTArray(const AutoSTArray&) = delete; AutoSTArray& operator=(AutoSTArray&&) = delete; AutoSTArray& operator=(const AutoSTArray&) = delete; /** Initialize with no objects */ AutoSTArray() { fArray = nullptr; fCount = 0; } /** Allocate count number of T elements */ AutoSTArray(int count) { fArray = nullptr; fCount = 0; this->reset(count); } ~AutoSTArray() { this->reset(0); } /** Destroys previous objects in the array and default constructs count number of objects */ void reset(int count) { T* start = fArray; T* iter = start + fCount; while (iter > start) { (--iter)->~T(); } SkASSERT(count >= 0); if (fCount != count) { if (fCount > kCount) { // 'fArray' was allocated last time so free it now SkASSERT((T*) fStorage != fArray); sk_free(fArray); } if (count > kCount) { fArray = (T*) sk_malloc_throw(count, sizeof(T)); } else if (count > 0) { fArray = (T*) fStorage; } else { fArray = nullptr; } fCount = count; } iter = fArray; T* stop = fArray + count; while (iter < stop) { new (iter++) T; } } /** Return the number of T elements in the array */ int count() const { return fCount; } /** Return the array of T elements. Will be NULL if count == 0 */ T* get() const { return fArray; } T* begin() { return fArray; } const T* begin() const { return fArray; } T* end() { return fArray + fCount; } const T* end() const { return fArray + fCount; } /** Return the nth element in the array */ T& operator[](int index) const { return fArray[sk_collection_check_bounds(index, fCount)]; } /** Aliases matching other types, like std::vector. */ const T* data() const { return fArray; } T* data() { return fArray; } size_t size() const { return fCount; } private: #if defined(SK_BUILD_FOR_GOOGLE3) // Stack frame size is limited for SK_BUILD_FOR_GOOGLE3. 4k is less than the actual max, // but some functions have multiple large stack allocations. static const int kMaxBytes = 4 * 1024; static const int kCount = kCountRequested * sizeof(T) > kMaxBytes ? kMaxBytes / sizeof(T) : kCountRequested; #else static const int kCount = kCountRequested; #endif int fCount; T* fArray; alignas(T) char fStorage[kCount * sizeof(T)]; }; /** Manages an array of T elements, freeing the array in the destructor. * Does NOT call any constructors/destructors on T (T must be POD). */ template ::value && std::is_trivially_destructible::value>> class AutoTMalloc { public: /** Takes ownership of the ptr. The ptr must be a value which can be passed to sk_free. */ explicit AutoTMalloc(T* ptr = nullptr) : fPtr(ptr) {} /** Allocates space for 'count' Ts. */ explicit AutoTMalloc(size_t count) : fPtr(count ? (T*)sk_malloc_throw(count, sizeof(T)) : nullptr) {} AutoTMalloc(AutoTMalloc&&) = default; AutoTMalloc& operator=(AutoTMalloc&&) = default; /** Resize the memory area pointed to by the current ptr preserving contents. */ void realloc(size_t count) { fPtr.reset(count ? (T*)sk_realloc_throw(fPtr.release(), count * sizeof(T)) : nullptr); } /** Resize the memory area pointed to by the current ptr without preserving contents. */ T* reset(size_t count = 0) { fPtr.reset(count ? (T*)sk_malloc_throw(count, sizeof(T)) : nullptr); return this->get(); } T* get() const { return fPtr.get(); } operator T*() { return fPtr.get(); } operator const T*() const { return fPtr.get(); } T& operator[](int index) { return fPtr.get()[index]; } const T& operator[](int index) const { return fPtr.get()[index]; } /** Aliases matching other types, like std::vector. */ const T* data() const { return fPtr.get(); } T* data() { return fPtr.get(); } /** * Transfer ownership of the ptr to the caller, setting the internal * pointer to NULL. Note that this differs from get(), which also returns * the pointer, but it does not transfer ownership. */ T* release() { return fPtr.release(); } private: std::unique_ptr> fPtr; }; template ::value && std::is_trivially_destructible::value>> class AutoSTMalloc { public: AutoSTMalloc() : fPtr(fTStorage) {} AutoSTMalloc(size_t count) { if (count > kCount) { fPtr = (T*)sk_malloc_throw(count, sizeof(T)); } else if (count) { fPtr = fTStorage; } else { fPtr = nullptr; } } AutoSTMalloc(AutoSTMalloc&&) = delete; AutoSTMalloc(const AutoSTMalloc&) = delete; AutoSTMalloc& operator=(AutoSTMalloc&&) = delete; AutoSTMalloc& operator=(const AutoSTMalloc&) = delete; ~AutoSTMalloc() { if (fPtr != fTStorage) { sk_free(fPtr); } } // doesn't preserve contents T* reset(size_t count) { if (fPtr != fTStorage) { sk_free(fPtr); } if (count > kCount) { fPtr = (T*)sk_malloc_throw(count, sizeof(T)); } else if (count) { fPtr = fTStorage; } else { fPtr = nullptr; } return fPtr; } T* get() const { return fPtr; } operator T*() { return fPtr; } operator const T*() const { return fPtr; } T& operator[](int index) { return fPtr[index]; } const T& operator[](int index) const { return fPtr[index]; } /** Aliases matching other types, like std::vector. */ const T* data() const { return fPtr; } T* data() { return fPtr; } // Reallocs the array, can be used to shrink the allocation. Makes no attempt to be intelligent void realloc(size_t count) { if (count > kCount) { if (fPtr == fTStorage) { fPtr = (T*)sk_malloc_throw(count, sizeof(T)); memcpy((void*)fPtr, fTStorage, kCount * sizeof(T)); } else { fPtr = (T*)sk_realloc_throw(fPtr, count, sizeof(T)); } } else if (count) { if (fPtr != fTStorage) { fPtr = (T*)sk_realloc_throw(fPtr, count, sizeof(T)); } } else { this->reset(0); } } private: // Since we use uint32_t storage, we might be able to get more elements for free. static const size_t kCountWithPadding = SkAlign4(kCountRequested*sizeof(T)) / sizeof(T); #if defined(SK_BUILD_FOR_GOOGLE3) // Stack frame size is limited for SK_BUILD_FOR_GOOGLE3. 4k is less than the actual max, but some functions // have multiple large stack allocations. static const size_t kMaxBytes = 4 * 1024; static const size_t kCount = kCountRequested * sizeof(T) > kMaxBytes ? kMaxBytes / sizeof(T) : kCountWithPadding; #else static const size_t kCount = kCountWithPadding; #endif T* fPtr; union { uint32_t fStorage32[SkAlign4(kCount*sizeof(T)) >> 2]; T fTStorage[1]; // do NOT want to invoke T::T() }; }; using UniqueVoidPtr = std::unique_ptr>; } // namespace skia_private template constexpr auto SkMakeArrayFromIndexSequence(C c, std::index_sequence is) -> std::array())), sizeof...(Is)> { return {{ c(Is)... }}; } template constexpr auto SkMakeArray(C c) -> std::array::value_type>())), N> { return SkMakeArrayFromIndexSequence(c, std::make_index_sequence{}); } /** @return x pinned (clamped) between lo and hi, inclusively. Unlike std::clamp(), SkTPin() always returns a value between lo and hi. If x is NaN, SkTPin() returns lo but std::clamp() returns NaN. */ template static constexpr const T& SkTPin(const T& x, const T& lo, const T& hi) { return std::max(lo, std::min(x, hi)); } class SK_API SkParse { public: static int Count(const char str[]); // number of scalars or int values static int Count(const char str[], char separator); static const char* FindColor(const char str[], SkColor* value); static const char* FindHex(const char str[], uint32_t* value); static const char* FindMSec(const char str[], SkMSec* value); static const char* FindNamedColor(const char str[], size_t len, SkColor* color); static const char* FindS32(const char str[], int32_t* value); static const char* FindScalar(const char str[], SkScalar* value); static const char* FindScalars(const char str[], SkScalar value[], int count); static bool FindBool(const char str[], bool* value); // return the index of str in list[], or -1 if not found static int FindList(const char str[], const char list[]); }; #ifdef __SANITIZE_ADDRESS__ #define SK_SANITIZE_ADDRESS 1 #endif #if !defined(SK_SANITIZE_ADDRESS) && defined(__has_feature) #if __has_feature(address_sanitizer) #define SK_SANITIZE_ADDRESS 1 #endif #endif // Typically declared in LLVM's asan_interface.h. #ifdef SK_SANITIZE_ADDRESS extern "C" { void __asan_poison_memory_region(void const volatile *addr, size_t size); void __asan_unpoison_memory_region(void const volatile *addr, size_t size); } #endif // Code that implements bespoke allocation arenas can poison the entire arena on creation, then // unpoison chunks of arena memory as they are parceled out. Consider leaving gaps between blocks // to detect buffer overrun. static inline void sk_asan_poison_memory_region(void const volatile *addr, size_t size) { #ifdef SK_SANITIZE_ADDRESS __asan_poison_memory_region(addr, size); #endif } static inline void sk_asan_unpoison_memory_region(void const volatile *addr, size_t size) { #ifdef SK_SANITIZE_ADDRESS __asan_unpoison_memory_region(addr, size); #endif } // We found allocating strictly doubling amounts of memory from the heap left too // much unused slop, particularly on Android. Instead we'll follow a Fibonacci-like // progression. // SkFibonacci47 is the first 47 Fibonacci numbers. Fib(47) is the largest value less than 2 ^ 32. extern std::array SkFibonacci47; template class SkFibBlockSizes { public: // staticBlockSize, and firstAllocationSize are parameters describing the initial memory // layout. staticBlockSize describes the size of the inlined memory, and firstAllocationSize // describes the size of the first block to be allocated if the static block is exhausted. By // convention, firstAllocationSize is the first choice for the block unit size followed by // staticBlockSize followed by the default of 1024 bytes. SkFibBlockSizes(uint32_t staticBlockSize, uint32_t firstAllocationSize) : fIndex{0} { fBlockUnitSize = firstAllocationSize > 0 ? firstAllocationSize : staticBlockSize > 0 ? staticBlockSize : 1024; SkASSERT_RELEASE(0 < fBlockUnitSize); SkASSERT_RELEASE(fBlockUnitSize < std::min(kMaxSize, (1u << 26) - 1)); } uint32_t nextBlockSize() { uint32_t result = SkFibonacci47[fIndex] * fBlockUnitSize; if (SkTo(fIndex + 1) < SkFibonacci47.size() && SkFibonacci47[fIndex + 1] < kMaxSize / fBlockUnitSize) { fIndex += 1; } return result; } private: uint32_t fIndex : 6; uint32_t fBlockUnitSize : 26; }; // SkArenaAlloc allocates object and destroys the allocated objects when destroyed. It's designed // to minimize the number of underlying block allocations. SkArenaAlloc allocates first out of an // (optional) user-provided block of memory, and when that's exhausted it allocates on the heap, // starting with an allocation of firstHeapAllocation bytes. If your data (plus a small overhead) // fits in the user-provided block, SkArenaAlloc never uses the heap, and if it fits in // firstHeapAllocation bytes, it'll use the heap only once. If 0 is specified for // firstHeapAllocation, then blockSize is used unless that too is 0, then 1024 is used. // // Examples: // // char block[mostCasesSize]; // SkArenaAlloc arena(block, mostCasesSize); // // If mostCasesSize is too large for the stack, you can use the following pattern. // // std::unique_ptr block{new char[mostCasesSize]}; // SkArenaAlloc arena(block.get(), mostCasesSize, almostAllCasesSize); // // If the program only sometimes allocates memory, use the following pattern. // // SkArenaAlloc arena(nullptr, 0, almostAllCasesSize); // // The storage does not necessarily need to be on the stack. Embedding the storage in a class also // works. // // class Foo { // char storage[mostCasesSize]; // SkArenaAlloc arena (storage, mostCasesSize); // }; // // In addition, the system is optimized to handle POD data including arrays of PODs (where // POD is really data with no destructors). For POD data it has zero overhead per item, and a // typical per block overhead of 8 bytes. For non-POD objects there is a per item overhead of 4 // bytes. For arrays of non-POD objects there is a per array overhead of typically 8 bytes. There // is an addition overhead when switching from POD data to non-POD data of typically 8 bytes. // // If additional blocks are needed they are increased exponentially. This strategy bounds the // recursion of the RunDtorsOnBlock to be limited to O(log size-of-memory). Block size grow using // the Fibonacci sequence which means that for 2^32 memory there are 48 allocations, and for 2^48 // there are 71 allocations. class SkArenaAlloc { public: SkArenaAlloc(char* block, size_t blockSize, size_t firstHeapAllocation); explicit SkArenaAlloc(size_t firstHeapAllocation) : SkArenaAlloc(nullptr, 0, firstHeapAllocation) {} SkArenaAlloc(const SkArenaAlloc&) = delete; SkArenaAlloc& operator=(const SkArenaAlloc&) = delete; SkArenaAlloc(SkArenaAlloc&&) = delete; SkArenaAlloc& operator=(SkArenaAlloc&&) = delete; ~SkArenaAlloc(); template auto make(Ctor&& ctor) -> decltype(ctor(nullptr)) { using T = std::remove_pointer_t; uint32_t size = SkToU32(sizeof(T)); uint32_t alignment = SkToU32(alignof(T)); char* objStart; if (std::is_trivially_destructible::value) { objStart = this->allocObject(size, alignment); fCursor = objStart + size; sk_asan_unpoison_memory_region(objStart, size); } else { objStart = this->allocObjectWithFooter(size + sizeof(Footer), alignment); // Can never be UB because max value is alignof(T). uint32_t padding = SkToU32(objStart - fCursor); // Advance to end of object to install footer. fCursor = objStart + size; sk_asan_unpoison_memory_region(objStart, size); FooterAction* releaser = [](char* objEnd) { char* objStart = objEnd - (sizeof(T) + sizeof(Footer)); ((T*)objStart)->~T(); return objStart; }; this->installFooter(releaser, padding); } // This must be last to make objects with nested use of this allocator work. return ctor(objStart); } template T* make(Args&&... args) { return this->make([&](void* objStart) { return new(objStart) T(std::forward(args)...); }); } template T* make() { if constexpr (std::is_standard_layout::value && std::is_trivial::value) { // Just allocate some aligned bytes. This generates smaller code. return (T*)this->makeBytesAlignedTo(sizeof(T), alignof(T)); } else { // This isn't a POD type, so construct the object properly. return this->make([&](void* objStart) { return new(objStart) T(); }); } } template T* makeArrayDefault(size_t count) { T* array = this->allocUninitializedArray(count); for (size_t i = 0; i < count; i++) { // Default initialization: if T is primitive then the value is left uninitialized. new (&array[i]) T; } return array; } template T* makeArray(size_t count) { T* array = this->allocUninitializedArray(count); for (size_t i = 0; i < count; i++) { // Value initialization: if T is primitive then the value is zero-initialized. new (&array[i]) T(); } return array; } template T* makeInitializedArray(size_t count, Initializer initializer) { T* array = this->allocUninitializedArray(count); for (size_t i = 0; i < count; i++) { new (&array[i]) T(initializer(i)); } return array; } // Only use makeBytesAlignedTo if none of the typed variants are practical to use. void* makeBytesAlignedTo(size_t size, size_t align) { AssertRelease(SkTFitsIn(size)); auto objStart = this->allocObject(SkToU32(size), SkToU32(align)); fCursor = objStart + size; sk_asan_unpoison_memory_region(objStart, size); return objStart; } protected: using FooterAction = char* (char*); struct Footer { uint8_t unaligned_action[sizeof(FooterAction*)]; uint8_t padding; }; char* cursor() { return fCursor; } char* end() { return fEnd; } private: static void AssertRelease(bool cond) { if (!cond) { ::abort(); } } static char* SkipPod(char* footerEnd); static void RunDtorsOnBlock(char* footerEnd); static char* NextBlock(char* footerEnd); template void installRaw(const T& val) { sk_asan_unpoison_memory_region(fCursor, sizeof(val)); memcpy(fCursor, &val, sizeof(val)); fCursor += sizeof(val); } void installFooter(FooterAction* releaser, uint32_t padding); void ensureSpace(uint32_t size, uint32_t alignment); char* allocObject(uint32_t size, uint32_t alignment) { uintptr_t mask = alignment - 1; uintptr_t alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; uintptr_t totalSize = size + alignedOffset; AssertRelease(totalSize >= size); if (totalSize > static_cast(fEnd - fCursor)) { this->ensureSpace(size, alignment); alignedOffset = (~reinterpret_cast(fCursor) + 1) & mask; } char* object = fCursor + alignedOffset; SkASSERT((reinterpret_cast(object) & (alignment - 1)) == 0); SkASSERT(object + size <= fEnd); return object; } char* allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment); template T* allocUninitializedArray(size_t countZ) { AssertRelease(SkTFitsIn(countZ)); uint32_t count = SkToU32(countZ); char* objStart; AssertRelease(count <= std::numeric_limits::max() / sizeof(T)); uint32_t arraySize = SkToU32(count * sizeof(T)); uint32_t alignment = SkToU32(alignof(T)); if (std::is_trivially_destructible::value) { objStart = this->allocObject(arraySize, alignment); fCursor = objStart + arraySize; sk_asan_unpoison_memory_region(objStart, arraySize); } else { constexpr uint32_t overhead = sizeof(Footer) + sizeof(uint32_t); AssertRelease(arraySize <= std::numeric_limits::max() - overhead); uint32_t totalSize = arraySize + overhead; objStart = this->allocObjectWithFooter(totalSize, alignment); // Can never be UB because max value is alignof(T). uint32_t padding = SkToU32(objStart - fCursor); // Advance to end of array to install footer. fCursor = objStart + arraySize; sk_asan_unpoison_memory_region(objStart, arraySize); this->installRaw(SkToU32(count)); this->installFooter( [](char* footerEnd) { char* objEnd = footerEnd - (sizeof(Footer) + sizeof(uint32_t)); uint32_t count; memmove(&count, objEnd, sizeof(uint32_t)); char* objStart = objEnd - count * sizeof(T); T* array = (T*) objStart; for (uint32_t i = 0; i < count; i++) { array[i].~T(); } return objStart; }, padding); } return (T*)objStart; } char* fDtorCursor; char* fCursor; char* fEnd; SkFibBlockSizes::max()> fFibonacciProgression; }; class SkArenaAllocWithReset : public SkArenaAlloc { public: SkArenaAllocWithReset(char* block, size_t blockSize, size_t firstHeapAllocation); explicit SkArenaAllocWithReset(size_t firstHeapAllocation) : SkArenaAllocWithReset(nullptr, 0, firstHeapAllocation) {} // Destroy all allocated objects, free any heap allocations. void reset(); // Returns true if the alloc has never made any objects. bool isEmpty(); private: char* const fFirstBlock; const uint32_t fFirstSize; const uint32_t fFirstHeapAllocationSize; }; // Helper for defining allocators with inline/reserved storage. // For argument declarations, stick to the base type (SkArenaAlloc). // Note: Inheriting from the storage first means the storage will outlive the // SkArenaAlloc, letting ~SkArenaAlloc read it as it calls destructors. // (This is mostly only relevant for strict tools like MSAN.) template class SkSTArenaAlloc : private std::array, public SkArenaAlloc { public: explicit SkSTArenaAlloc(size_t firstHeapAllocation = InlineStorageSize) : SkArenaAlloc{this->data(), this->size(), firstHeapAllocation} {} ~SkSTArenaAlloc() { // Be sure to unpoison the memory that is probably on the stack. sk_asan_unpoison_memory_region(this->data(), this->size()); } }; template class SkSTArenaAllocWithReset : private std::array, public SkArenaAllocWithReset { public: explicit SkSTArenaAllocWithReset(size_t firstHeapAllocation = InlineStorageSize) : SkArenaAllocWithReset{this->data(), this->size(), firstHeapAllocation} {} ~SkSTArenaAllocWithReset() { // Be sure to unpoison the memory that is probably on the stack. sk_asan_unpoison_memory_region(this->data(), this->size()); } }; struct SkPoint; /** * Utilities for dealing with cubic Bézier curves. These have a start XY * point, an end XY point, and two control XY points in between. They take * a parameter t which is between 0 and 1 (inclusive) which is used to * interpolate between the start and end points, via a route dictated by * the control points, and return a new XY point. * * We store a Bézier curve as an array of 8 floats or doubles, where * the even indices are the X coordinates, and the odd indices are the Y * coordinates. */ class SkBezierCubic { public: /** * Evaluates the cubic Bézier curve for a given t. It returns an X and Y coordinate * following the formula, which does the interpolation mentioned above. * X(t) = X_0*(1-t)^3 + 3*X_1*t(1-t)^2 + 3*X_2*t^2(1-t) + X_3*t^3 * Y(t) = Y_0*(1-t)^3 + 3*Y_1*t(1-t)^2 + 3*Y_2*t^2(1-t) + Y_3*t^3 * * t is typically in the range [0, 1], but this function will not assert that, * as Bézier curves are well-defined for any real number input. */ static std::array EvalAt(const double curve[8], double t); /** * Splits the provided Bézier curve at the location t, resulting in two * Bézier curves that share a point (the end point from curve 1 * and the start point from curve 2 are the same). * * t must be in the interval [0, 1]. * * The provided twoCurves array will be filled such that indices * 0-7 are the first curve (representing the interval [0, t]), and * indices 6-13 are the second curve (representing [t, 1]). */ static void Subdivide(const double curve[8], double t, double twoCurves[14]); /** * Converts the provided Bézier curve into the the equivalent cubic * f(t) = A*t^3 + B*t^2 + C*t + D * where f(t) will represent Y coordinates over time if yValues is * true and the X coordinates if yValues is false. * * In effect, this turns the control points into an actual line, representing * the x or y values. */ static std::array ConvertToPolynomial(const double curve[8], bool yValues); static SkSpan IntersectWithHorizontalLine( SkSpan controlPoints, float yIntercept, float intersectionStorage[3]); static SkSpan Intersect( double AX, double BX, double CX, double DX, double AY, double BY, double CY, double DY, float toIntersect, float intersectionsStorage[3]); }; class SkBezierQuad { public: static SkSpan IntersectWithHorizontalLine( SkSpan controlPoints, float yIntercept, float intersectionStorage[2]); /** * Given * AY*t^2 -2*BY*t + CY = 0 and AX*t^2 - 2*BX*t + CX = 0, * * Find the t where AY*t^2 - 2*BY*t + CY - y = 0, then return AX*t^2 + - 2*BX*t + CX * where t is on [0, 1]. * * - y - is the height of the line which intersects the quadratic. * - intersectionStorage - is the array to hold the return data pointed to in the span. * * Returns a span with the intersections of yIntercept, and the quadratic formed by A, B, * and C. */ static SkSpan Intersect( double AX, double BX, double CX, double AY, double BY, double CY, double yIntercept, float intersectionStorage[2]); }; /** * Utilities for dealing with cubic formulas with one variable: * f(t) = A*t^3 + B*t^2 + C*t + d */ class SkCubics { public: /** * Puts up to 3 real solutions to the equation * A*t^3 + B*t^2 + C*t + d = 0 * in the provided array and returns how many roots that was. */ static int RootsReal(double A, double B, double C, double D, double solution[3]); /** * Puts up to 3 real solutions to the equation * A*t^3 + B*t^2 + C*t + D = 0 * in the provided array, with the constraint that t is in the range [0.0, 1.0], * and returns how many roots that was. */ static int RootsValidT(double A, double B, double C, double D, double solution[3]); /** * Puts up to 3 real solutions to the equation * A*t^3 + B*t^2 + C*t + D = 0 * in the provided array, with the constraint that t is in the range [0.0, 1.0], * and returns how many roots that was. * This is a slower method than RootsValidT, but more accurate in circumstances * where floating point error gets too big. */ static int BinarySearchRootsValidT(double A, double B, double C, double D, double solution[3]); /** * Evaluates the cubic function with the 4 provided coefficients and the * provided variable. */ static double EvalAt(double A, double B, double C, double D, double t) { return std::fma(t, std::fma(t, std::fma(t, A, B), C), D); } static double EvalAt(double coefficients[4], double t) { return EvalAt(coefficients[0], coefficients[1], coefficients[2], coefficients[3], t); } }; /** * Utilities for dealing with quadratic formulas with one variable: * f(t) = A*t^2 + B*t + C */ class SkQuads { public: /** * Calculate a very accurate discriminant. * Given * A*t^2 -2*B*t + C = 0, * calculate * B^2 - AC * accurate to 2 bits. * Note the form of the quadratic is slightly different from the normal formulation. * * The method used to calculate the discriminant is from * "On the Cost of Floating-Point Computation Without Extra-Precise Arithmetic" * by W. Kahan. */ static double Discriminant(double A, double B, double C); struct RootResult { double discriminant; double root0; double root1; }; /** * Calculate the roots of a quadratic. * Given * A*t^2 -2*B*t + C = 0, * calculate the roots. * * This does not try to detect a linear configuration of the equation, or detect if the two * roots are the same. It returns the discriminant and the two roots. * * Not this uses a different form the quadratic equation to reduce rounding error. Give * standard A, B, C. You can call this root finder with: * Roots(A, -0.5*B, C) * to find the roots of A*x^2 + B*x + C. * * The method used to calculate the roots is from * "On the Cost of Floating-Point Computation Without Extra-Precise Arithmetic" * by W. Kahan. * * If the roots are imaginary then nan is returned. * If the roots can't be represented as double then inf is returned. */ static RootResult Roots(double A, double B, double C); /** * Puts up to 2 real solutions to the equation * A*t^2 + B*t + C = 0 * in the provided array. */ static int RootsReal(double A, double B, double C, double solution[2]); /** * Evaluates the quadratic function with the 3 provided coefficients and the * provided variable. */ static double EvalAt(double A, double B, double C, double t); }; #ifndef SkSafeMath_DEFINED #define SkSafeMath_DEFINED // SkSafeMath always check that a series of operations do not overflow. // This must be correct for all platforms, because this is a check for safety at runtime. class SkSafeMath { public: SkSafeMath() = default; bool ok() const { return fOK; } explicit operator bool() const { return fOK; } size_t mul(size_t x, size_t y) { return sizeof(size_t) == sizeof(uint64_t) ? mul64(x, y) : mul32(x, y); } size_t add(size_t x, size_t y) { size_t result = x + y; fOK &= result >= x; return result; } /** * Return a + b, unless this result is an overflow/underflow. In those cases, fOK will * be set to false, and it is undefined what this returns. */ int addInt(int a, int b) { if (b < 0 && a < std::numeric_limits::min() - b) { fOK = false; return a; } else if (b > 0 && a > std::numeric_limits::max() - b) { fOK = false; return a; } return a + b; } size_t alignUp(size_t x, size_t alignment) { SkASSERT(alignment && !(alignment & (alignment - 1))); return add(x, alignment - 1) & ~(alignment - 1); } template T castTo(size_t value) { if (!SkTFitsIn(value)) { fOK = false; } return static_cast(value); } // These saturate to their results static size_t Add(size_t x, size_t y); static size_t Mul(size_t x, size_t y); static size_t Align4(size_t x) { SkSafeMath safe; return safe.alignUp(x, 4); } private: uint32_t mul32(uint32_t x, uint32_t y) { uint64_t bx = x; uint64_t by = y; uint64_t result = bx * by; fOK &= result >> 32 == 0; // Overflow information is capture in fOK. Return the result modulo 2^32. return (uint32_t)result; } uint64_t mul64(uint64_t x, uint64_t y) { if (x <= std::numeric_limits::max() >> 32 && y <= std::numeric_limits::max() >> 32) { return x * y; } else { auto hi = [](uint64_t x) { return x >> 32; }; auto lo = [](uint64_t x) { return x & 0xFFFFFFFF; }; uint64_t lx_ly = lo(x) * lo(y); uint64_t hx_ly = hi(x) * lo(y); uint64_t lx_hy = lo(x) * hi(y); uint64_t hx_hy = hi(x) * hi(y); uint64_t result = 0; result = this->add(lx_ly, (hx_ly << 32)); result = this->add(result, (lx_hy << 32)); fOK &= (hx_hy + (hx_ly >> 32) + (lx_hy >> 32)) == 0; #if defined(SK_DEBUG) && defined(__clang__) && defined(__x86_64__) auto double_check = (unsigned __int128)x * y; SkASSERT(result == (double_check & 0xFFFFFFFFFFFFFFFF)); SkASSERT(!fOK || (double_check >> 64 == 0)); #endif return result; } } bool fOK = true; }; #endif//SkSafeMath_DEFINED typedef float SkScalar; /** \class SkRBuffer Light weight class for reading data from a memory block. The RBuffer is given the buffer to read from, with either a specified size or no size (in which case no range checking is performed). It is iillegal to attempt to read a value from an empty RBuffer (data == null). */ class SkRBuffer : SkNoncopyable { public: SkRBuffer() : fData(nullptr), fPos(nullptr), fStop(nullptr) {} /** Initialize RBuffer with a data point and length. */ SkRBuffer(const void* data, size_t size) { SkASSERT(data != nullptr || size == 0); fData = (const char*)data; fPos = (const char*)data; fStop = (const char*)data + size; } /** Return the number of bytes that have been read from the beginning of the data pointer. */ size_t pos() const { return fPos - fData; } /** Return the total size of the data pointer. Only defined if the length was specified in the constructor or in a call to reset(). */ size_t size() const { return fStop - fData; } /** Return true if the buffer has read to the end of the data pointer. Only defined if the length was specified in the constructor or in a call to reset(). Always returns true if the length was not specified. */ bool eof() const { return fPos >= fStop; } size_t available() const { return fStop - fPos; } bool isValid() const { return fValid; } /** Read the specified number of bytes from the data pointer. If buffer is not null, copy those bytes into buffer. */ bool read(void* buffer, size_t size); bool skipToAlign4(); bool readU8(uint8_t* x) { return this->read(x, 1); } bool readS32(int32_t* x) { return this->read(x, 4); } bool readU32(uint32_t* x) { return this->read(x, 4); } // returns nullptr on failure const void* skip(size_t bytes); template const T* skipCount(size_t count) { return static_cast(this->skip(SkSafeMath::Mul(count, sizeof(T)))); } private: const char* fData; const char* fPos; const char* fStop; bool fValid = true; }; /** \class SkWBuffer Light weight class for writing data to a memory block. The WBuffer is given the buffer to write into, with either a specified size or no size, in which case no range checking is performed. An empty WBuffer is legal, in which case no data is ever written, but the relative pos() is updated. */ class SkWBuffer : SkNoncopyable { public: SkWBuffer() : fData(nullptr), fPos(nullptr), fStop(nullptr) {} SkWBuffer(void* data) { reset(data); } SkWBuffer(void* data, size_t size) { reset(data, size); } void reset(void* data) { fData = (char*)data; fPos = (char*)data; fStop = nullptr; // no bounds checking } void reset(void* data, size_t size) { SkASSERT(data != nullptr || size == 0); fData = (char*)data; fPos = (char*)data; fStop = (char*)data + size; } size_t pos() const { return fPos - fData; } void* skip(size_t size); // return start of skipped data void write(const void* buffer, size_t size) { if (size) { this->writeNoSizeCheck(buffer, size); } } size_t padToAlign4(); void writePtr(const void* x) { this->writeNoSizeCheck(&x, sizeof(x)); } void writeScalar(SkScalar x) { this->writeNoSizeCheck(&x, 4); } void write32(int32_t x) { this->writeNoSizeCheck(&x, 4); } void write16(int16_t x) { this->writeNoSizeCheck(&x, 2); } void write8(int8_t x) { this->writeNoSizeCheck(&x, 1); } void writeBool(bool x) { this->write8(x); } private: void writeNoSizeCheck(const void* buffer, size_t size); char* fData; char* fPos; char* fStop; }; #ifdef SK_BUILD_FOR_WIN // https://devblogs.microsoft.com/oldnewthing/20091130-00/?p=15863 # ifndef WIN32_LEAN_AND_MEAN # define WIN32_LEAN_AND_MEAN # define WIN32_IS_MEAN_WAS_LOCALLY_DEFINED # endif # ifndef NOMINMAX # define NOMINMAX # define NOMINMAX_WAS_LOCALLY_DEFINED # endif # # # ifdef WIN32_IS_MEAN_WAS_LOCALLY_DEFINED # undef WIN32_IS_MEAN_WAS_LOCALLY_DEFINED # undef WIN32_LEAN_AND_MEAN # endif # ifdef NOMINMAX_WAS_LOCALLY_DEFINED # undef NOMINMAX_WAS_LOCALLY_DEFINED # undef NOMINMAX # endif #endif /** * Return the integer square root of value, with a bias of bitBias */ int32_t SkSqrtBits(int32_t value, int bitBias); /** Return the integer square root of n, treated as a SkFixed (16.16) */ static inline int32_t SkSqrt32(int32_t n) { return SkSqrtBits(n, 15); } /** * Returns (value < 0 ? 0 : value) efficiently (i.e. no compares or branches) */ static inline int SkClampPos(int value) { return value & ~(value >> 31); } /** * Stores numer/denom and numer%denom into div and mod respectively. */ template inline void SkTDivMod(In numer, In denom, Out* div, Out* mod) { #ifdef SK_CPU_ARM32 // If we wrote this as in the else branch, GCC won't fuse the two into one // divmod call, but rather a div call followed by a divmod. Silly! This // version is just as fast as calling __aeabi_[u]idivmod manually, but with // prettier code. // // This benches as around 2x faster than the code in the else branch. const In d = numer/denom; *div = static_cast(d); *mod = static_cast(numer-d*denom); #else // On x86 this will just be a single idiv. *div = static_cast(numer/denom); *mod = static_cast(numer%denom); #endif } /** Returns -1 if n < 0, else returns 0 */ #define SkExtractSign(n) ((int32_t)(n) >> 31) /** If sign == -1, returns -n, else sign must be 0, and returns n. Typically used in conjunction with SkExtractSign(). */ static inline int32_t SkApplySign(int32_t n, int32_t sign) { SkASSERT(sign == 0 || sign == -1); return (n ^ sign) - sign; } /** Return x with the sign of y */ static inline int32_t SkCopySign32(int32_t x, int32_t y) { return SkApplySign(x, SkExtractSign(x ^ y)); } /** Given a positive value and a positive max, return the value pinned against max. Note: only works as long as max - value doesn't wrap around @return max if value >= max, else value */ static inline unsigned SkClampUMax(unsigned value, unsigned max) { if (value > max) { value = max; } return value; } // If a signed int holds min_int (e.g. 0x80000000) it is undefined what happens when // we negate it (even though we *know* we're 2's complement and we'll get the same // value back). So we create this helper function that casts to size_t (unsigned) first, // to avoid the complaint. static inline size_t sk_negate_to_size_t(int32_t value) { #if defined(_MSC_VER) #pragma warning(push) #pragma warning(disable : 4146) // Thanks MSVC, we know what we're negating an unsigned #endif return -static_cast(value); #if defined(_MSC_VER) #pragma warning(pop) #endif } /////////////////////////////////////////////////////////////////////////////// /** Return a*b/255, truncating away any fractional bits. Only valid if both a and b are 0..255 */ static inline U8CPU SkMulDiv255Trunc(U8CPU a, U8CPU b) { SkASSERT((uint8_t)a == a); SkASSERT((uint8_t)b == b); unsigned prod = a*b + 1; return (prod + (prod >> 8)) >> 8; } /** Return (a*b)/255, taking the ceiling of any fractional bits. Only valid if both a and b are 0..255. The expected result equals (a * b + 254) / 255. */ static inline U8CPU SkMulDiv255Ceiling(U8CPU a, U8CPU b) { SkASSERT((uint8_t)a == a); SkASSERT((uint8_t)b == b); unsigned prod = a*b + 255; return (prod + (prod >> 8)) >> 8; } /** Just the rounding step in SkDiv255Round: round(value / 255) */ static inline unsigned SkDiv255Round(unsigned prod) { prod += 128; return (prod + (prod >> 8)) >> 8; } /** * Swap byte order of a 4-byte value, e.g. 0xaarrggbb -> 0xbbggrraa. */ #if defined(_MSC_VER) static inline uint32_t SkBSwap32(uint32_t v) { return _byteswap_ulong(v); } #else static inline uint32_t SkBSwap32(uint32_t v) { return __builtin_bswap32(v); } #endif /* * Return the number of set bits (i.e., the population count) in the provided uint32_t. */ int SkPopCount_portable(uint32_t n); #if defined(__GNUC__) || defined(__clang__) static inline int SkPopCount(uint32_t n) { return __builtin_popcount(n); } #else static inline int SkPopCount(uint32_t n) { return SkPopCount_portable(n); } #endif /* * Return the 0-based index of the nth bit set in target * Returns 32 if there is no nth bit set. */ int SkNthSet(uint32_t target, int n); //! Returns the number of leading zero bits (0...32) // From Hacker's Delight 2nd Edition constexpr int SkCLZ_portable(uint32_t x) { int n = 32; uint32_t y = x >> 16; if (y != 0) {n -= 16; x = y;} y = x >> 8; if (y != 0) {n -= 8; x = y;} y = x >> 4; if (y != 0) {n -= 4; x = y;} y = x >> 2; if (y != 0) {n -= 2; x = y;} y = x >> 1; if (y != 0) {return n - 2;} return n - static_cast(x); } static_assert(32 == SkCLZ_portable(0)); static_assert(31 == SkCLZ_portable(1)); static_assert( 1 == SkCLZ_portable(1 << 30)); static_assert( 1 == SkCLZ_portable((1 << 30) | (1 << 24) | 1)); static_assert( 0 == SkCLZ_portable(~0U)); #if defined(SK_BUILD_FOR_WIN) static inline int SkCLZ(uint32_t mask) { if (mask) { unsigned long index = 0; _BitScanReverse(&index, mask); // Suppress this bogus /analyze warning. The check for non-zero // guarantees that _BitScanReverse will succeed. #pragma warning(suppress : 6102) // Using 'index' from failed function call return index ^ 0x1F; } else { return 32; } } #elif defined(SK_CPU_ARM32) || defined(__GNUC__) || defined(__clang__) static inline int SkCLZ(uint32_t mask) { // __builtin_clz(0) is undefined, so we have to detect that case. return mask ? __builtin_clz(mask) : 32; } #else static inline int SkCLZ(uint32_t mask) { return SkCLZ_portable(mask); } #endif //! Returns the number of trailing zero bits (0...32) // From Hacker's Delight 2nd Edition constexpr int SkCTZ_portable(uint32_t x) { return 32 - SkCLZ_portable(~x & (x - 1)); } static_assert(32 == SkCTZ_portable(0)); static_assert( 0 == SkCTZ_portable(1)); static_assert(30 == SkCTZ_portable(1 << 30)); static_assert( 2 == SkCTZ_portable((1 << 30) | (1 << 24) | (1 << 2))); static_assert( 0 == SkCTZ_portable(~0U)); #if defined(SK_BUILD_FOR_WIN) static inline int SkCTZ(uint32_t mask) { if (mask) { unsigned long index = 0; _BitScanForward(&index, mask); // Suppress this bogus /analyze warning. The check for non-zero // guarantees that _BitScanReverse will succeed. #pragma warning(suppress : 6102) // Using 'index' from failed function call return index; } else { return 32; } } #elif defined(SK_CPU_ARM32) || defined(__GNUC__) || defined(__clang__) static inline int SkCTZ(uint32_t mask) { // __builtin_ctz(0) is undefined, so we have to detect that case. return mask ? __builtin_ctz(mask) : 32; } #else static inline int SkCTZ(uint32_t mask) { return SkCTZ_portable(mask); } #endif /** * Returns the log2 of the specified value, were that value to be rounded up * to the next power of 2. It is undefined to pass 0. Examples: * SkNextLog2(1) -> 0 * SkNextLog2(2) -> 1 * SkNextLog2(3) -> 2 * SkNextLog2(4) -> 2 * SkNextLog2(5) -> 3 */ static inline int SkNextLog2(uint32_t value) { SkASSERT(value != 0); return 32 - SkCLZ(value - 1); } constexpr int SkNextLog2_portable(uint32_t value) { SkASSERT(value != 0); return 32 - SkCLZ_portable(value - 1); } /** * Returns the log2 of the specified value, were that value to be rounded down * to the previous power of 2. It is undefined to pass 0. Examples: * SkPrevLog2(1) -> 0 * SkPrevLog2(2) -> 1 * SkPrevLog2(3) -> 1 * SkPrevLog2(4) -> 2 * SkPrevLog2(5) -> 2 */ static inline int SkPrevLog2(uint32_t value) { SkASSERT(value != 0); return 32 - SkCLZ(value >> 1); } constexpr int SkPrevLog2_portable(uint32_t value) { SkASSERT(value != 0); return 32 - SkCLZ_portable(value >> 1); } /** * Returns the smallest power-of-2 that is >= the specified value. If value * is already a power of 2, then it is returned unchanged. It is undefined * if value is <= 0. */ static inline int SkNextPow2(int value) { SkASSERT(value > 0); return 1 << SkNextLog2(static_cast(value)); } constexpr int SkNextPow2_portable(int value) { SkASSERT(value > 0); return 1 << SkNextLog2_portable(static_cast(value)); } /** * Returns the largest power-of-2 that is <= the specified value. If value * is already a power of 2, then it is returned unchanged. It is undefined * if value is <= 0. */ static inline int SkPrevPow2(int value) { SkASSERT(value > 0); return 1 << SkPrevLog2(static_cast(value)); } constexpr int SkPrevPow2_portable(int value) { SkASSERT(value > 0); return 1 << SkPrevLog2_portable(static_cast(value)); } /////////////////////////////////////////////////////////////////////////////// /** * Return the smallest power-of-2 >= n. */ static inline uint32_t GrNextPow2(uint32_t n) { return n ? (1 << (32 - SkCLZ(n - 1))) : 1; } /** * Returns the next power of 2 >= n or n if the next power of 2 can't be represented by size_t. */ static inline size_t GrNextSizePow2(size_t n) { constexpr int kNumSizeTBits = 8 * sizeof(size_t); constexpr size_t kHighBitSet = size_t(1) << (kNumSizeTBits - 1); if (!n) { return 1; } else if (n >= kHighBitSet) { return n; } n--; uint32_t shift = 1; while (shift < kNumSizeTBits) { n |= n >> shift; shift <<= 1; } return n + 1; } // conservative check. will return false for very large values that "could" fit template static inline bool SkFitsInFixed(T x) { return SkTAbs(x) <= 32767.0f; } /** * Efficient way to defer allocating/initializing a class until it is needed * (if ever). */ template class SkTLazy { public: SkTLazy() = default; explicit SkTLazy(const T* src) : fValue(src ? std::optional(*src) : std::nullopt) {} SkTLazy(const SkTLazy& that) : fValue(that.fValue) {} SkTLazy(SkTLazy&& that) : fValue(std::move(that.fValue)) {} ~SkTLazy() = default; SkTLazy& operator=(const SkTLazy& that) { fValue = that.fValue; return *this; } SkTLazy& operator=(SkTLazy&& that) { fValue = std::move(that.fValue); return *this; } /** * Return a pointer to an instance of the class initialized with 'args'. * If a previous instance had been initialized (either from init() or * set()) it will first be destroyed, so that a freshly initialized * instance is always returned. */ template T* init(Args&&... args) { fValue.emplace(std::forward(args)...); return this->get(); } /** * Copy src into this, and return a pointer to a copy of it. Note this * will always return the same pointer, so if it is called on a lazy that * has already been initialized, then this will copy over the previous * contents. */ T* set(const T& src) { fValue = src; return this->get(); } T* set(T&& src) { fValue = std::move(src); return this->get(); } /** * Destroy the lazy object (if it was created via init() or set()) */ void reset() { fValue.reset(); } /** * Returns true if a valid object has been initialized in the SkTLazy, * false otherwise. */ bool isValid() const { return fValue.has_value(); } /** * Returns the object. This version should only be called when the caller * knows that the object has been initialized. */ T* get() { SkASSERT(fValue.has_value()); return &fValue.value(); } const T* get() const { SkASSERT(fValue.has_value()); return &fValue.value(); } T* operator->() { return this->get(); } const T* operator->() const { return this->get(); } T& operator*() { SkASSERT(fValue.has_value()); return *fValue; } const T& operator*() const { SkASSERT(fValue.has_value()); return *fValue; } /** * Like above but doesn't assert if object isn't initialized (in which case * nullptr is returned). */ const T* getMaybeNull() const { return fValue.has_value() ? this->get() : nullptr; } T* getMaybeNull() { return fValue.has_value() ? this->get() : nullptr; } private: std::optional fValue; }; /** * A helper built on top of std::optional to do copy-on-first-write. The object is initialized * with a const pointer but provides a non-const pointer accessor. The first time the * accessor is called (if ever) the object is cloned. * * In the following example at most one copy of constThing is made: * * SkTCopyOnFirstWrite thing(&constThing); * ... * function_that_takes_a_const_thing_ptr(thing); // constThing is passed * ... * if (need_to_modify_thing()) { * thing.writable()->modifyMe(); // makes a copy of constThing * } * ... * x = thing->readSomething(); * ... * if (need_to_modify_thing_now()) { * thing.writable()->changeMe(); // makes a copy of constThing if we didn't call modifyMe() * } * * consume_a_thing(thing); // could be constThing or a modified copy. */ template class SkTCopyOnFirstWrite { public: explicit SkTCopyOnFirstWrite(const T& initial) : fObj(&initial) {} explicit SkTCopyOnFirstWrite(const T* initial) : fObj(initial) {} // Constructor for delayed initialization. SkTCopyOnFirstWrite() : fObj(nullptr) {} SkTCopyOnFirstWrite(const SkTCopyOnFirstWrite& that) { *this = that; } SkTCopyOnFirstWrite( SkTCopyOnFirstWrite&& that) { *this = std::move(that); } SkTCopyOnFirstWrite& operator=(const SkTCopyOnFirstWrite& that) { fLazy = that.fLazy; fObj = fLazy.has_value() ? &fLazy.value() : that.fObj; return *this; } SkTCopyOnFirstWrite& operator=(SkTCopyOnFirstWrite&& that) { fLazy = std::move(that.fLazy); fObj = fLazy.has_value() ? &fLazy.value() : that.fObj; return *this; } // Should only be called once, and only if the default constructor was used. void init(const T& initial) { SkASSERT(!fObj); SkASSERT(!fLazy.has_value()); fObj = &initial; } // If not already initialized, in-place instantiates the writable object template void initIfNeeded(Args&&... args) { if (!fObj) { SkASSERT(!fLazy.has_value()); fObj = &fLazy.emplace(std::forward(args)...); } } /** * Returns a writable T*. The first time this is called the initial object is cloned. */ T* writable() { SkASSERT(fObj); if (!fLazy.has_value()) { fLazy = *fObj; fObj = &fLazy.value(); } return &fLazy.value(); } const T* get() const { return fObj; } /** * Operators for treating this as though it were a const pointer. */ const T *operator->() const { return fObj; } operator const T*() const { return fObj; } const T& operator *() const { return *fObj; } private: const T* fObj; std::optional fLazy; }; /////////////////////////////////////////////////////////////////////////////// /* Sifts a broken heap. The input array is a heap from root to bottom * except that the root entry may be out of place. * * Sinks a hole from array[root] to leaf and then sifts the original array[root] element * from the leaf level up. * * This version does extra work, in that it copies child to parent on the way down, * then copies parent to child on the way back up. When copies are inexpensive, * this is an optimization as this sift variant should only be used when * the potentially out of place root entry value is expected to be small. * * @param root the one based index into array of the out-of-place root of the heap. * @param bottom the one based index in the array of the last entry in the heap. */ template void SkTHeapSort_SiftUp(T array[], size_t root, size_t bottom, const C& lessThan) { T x = array[root-1]; size_t start = root; size_t j = root << 1; while (j <= bottom) { if (j < bottom && lessThan(array[j-1], array[j])) { ++j; } array[root-1] = array[j-1]; root = j; j = root << 1; } j = root >> 1; while (j >= start) { if (lessThan(array[j-1], x)) { array[root-1] = array[j-1]; root = j; j = root >> 1; } else { break; } } array[root-1] = x; } /* Sifts a broken heap. The input array is a heap from root to bottom * except that the root entry may be out of place. * * Sifts the array[root] element from the root down. * * @param root the one based index into array of the out-of-place root of the heap. * @param bottom the one based index in the array of the last entry in the heap. */ template void SkTHeapSort_SiftDown(T array[], size_t root, size_t bottom, const C& lessThan) { T x = array[root-1]; size_t child = root << 1; while (child <= bottom) { if (child < bottom && lessThan(array[child-1], array[child])) { ++child; } if (lessThan(x, array[child-1])) { array[root-1] = array[child-1]; root = child; child = root << 1; } else { break; } } array[root-1] = x; } /** Sorts the array of size count using comparator lessThan using a Heap Sort algorithm. Be sure to * specialize swap if T has an efficient swap operation. * * @param array the array to be sorted. * @param count the number of elements in the array. * @param lessThan a functor with bool operator()(T a, T b) which returns true if a comes before b. */ template void SkTHeapSort(T array[], size_t count, const C& lessThan) { for (size_t i = count >> 1; i > 0; --i) { SkTHeapSort_SiftDown(array, i, count, lessThan); } for (size_t i = count - 1; i > 0; --i) { using std::swap; swap(array[0], array[i]); SkTHeapSort_SiftUp(array, 1, i, lessThan); } } /** Sorts the array of size count using comparator '<' using a Heap Sort algorithm. */ template void SkTHeapSort(T array[], size_t count) { SkTHeapSort(array, count, [](const T& a, const T& b) { return a < b; }); } /////////////////////////////////////////////////////////////////////////////// /** Sorts the array of size count using comparator lessThan using an Insertion Sort algorithm. */ template void SkTInsertionSort(T* left, int count, const C& lessThan) { T* right = left + count - 1; for (T* next = left + 1; next <= right; ++next) { if (!lessThan(*next, *(next - 1))) { continue; } T insert = std::move(*next); T* hole = next; do { *hole = std::move(*(hole - 1)); --hole; } while (left < hole && lessThan(insert, *(hole - 1))); *hole = std::move(insert); } } /////////////////////////////////////////////////////////////////////////////// template T* SkTQSort_Partition(T* left, int count, T* pivot, const C& lessThan) { T* right = left + count - 1; using std::swap; T pivotValue = *pivot; swap(*pivot, *right); T* newPivot = left; while (left < right) { if (lessThan(*left, pivotValue)) { swap(*left, *newPivot); newPivot += 1; } left += 1; } swap(*newPivot, *right); return newPivot; } /* Introsort is a modified Quicksort. * When the region to be sorted is a small constant size, it uses Insertion Sort. * When depth becomes zero, it switches over to Heap Sort. * This implementation recurses on the left region after pivoting and loops on the right, * we already limit the stack depth by switching to heap sort, * and cache locality on the data appears more important than saving a few stack frames. * * @param depth at this recursion depth, switch to Heap Sort. * @param left points to the beginning of the region to be sorted * @param count number of items to be sorted * @param lessThan a functor/lambda which returns true if a comes before b. */ template void SkTIntroSort(int depth, T* left, int count, const C& lessThan) { for (;;) { if (count <= 32) { SkTInsertionSort(left, count, lessThan); return; } if (depth == 0) { SkTHeapSort(left, count, lessThan); return; } --depth; T* middle = left + ((count - 1) >> 1); T* pivot = SkTQSort_Partition(left, count, middle, lessThan); int pivotCount = pivot - left; SkTIntroSort(depth, left, pivotCount, lessThan); left += pivotCount + 1; count -= pivotCount + 1; } } /** Sorts the region from left to right using comparator lessThan using Introsort. * Be sure to specialize `swap` if T has an efficient swap operation. * * @param begin points to the beginning of the region to be sorted * @param end points past the end of the region to be sorted * @param lessThan a functor/lambda which returns true if a comes before b. */ template void SkTQSort(T* begin, T* end, const C& lessThan) { int n = SkToInt(end - begin); if (n <= 1) { return; } // Limit Introsort recursion depth to no more than 2 * ceil(log2(n-1)). int depth = 2 * SkNextLog2(n - 1); SkTIntroSort(depth, begin, n, lessThan); } /** Sorts the region from left to right using comparator 'a < b' using Introsort. */ template void SkTQSort(T* begin, T* end) { SkTQSort(begin, end, [](const T& a, const T& b) { return a < b; }); } /** Sorts the region from left to right using comparator '*a < *b' using Introsort. */ template void SkTQSort(T** begin, T** end) { SkTQSort(begin, end, [](const T* a, const T* b) { return *a < *b; }); } // Copyright 2018 Google LLC. // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. #ifndef SkUTF_DEFINED #define SkUTF_DEFINED typedef int32_t SkUnichar; namespace SkUTF { /** Given a sequence of UTF-8 bytes, return the number of unicode codepoints. If the sequence is invalid UTF-8, return -1. */ SK_SPI int CountUTF8(const char* utf8, size_t byteLength); /** Given a sequence of aligned UTF-16 characters in machine-endian form, return the number of unicode codepoints. If the sequence is invalid UTF-16, return -1. */ SK_SPI int CountUTF16(const uint16_t* utf16, size_t byteLength); /** Given a sequence of aligned UTF-32 characters in machine-endian form, return the number of unicode codepoints. If the sequence is invalid UTF-32, return -1. */ SK_SPI int CountUTF32(const int32_t* utf32, size_t byteLength); /** Given a sequence of UTF-8 bytes, return the first unicode codepoint. The pointer will be incremented to point at the next codepoint's start. If invalid UTF-8 is encountered, set *ptr to end and return -1. */ SK_SPI SkUnichar NextUTF8(const char** ptr, const char* end); /** Given a sequence of aligned UTF-16 characters in machine-endian form, return the first unicode codepoint. The pointer will be incremented to point at the next codepoint's start. If invalid UTF-16 is encountered, set *ptr to end and return -1. */ SK_SPI SkUnichar NextUTF16(const uint16_t** ptr, const uint16_t* end); /** Given a sequence of aligned UTF-32 characters in machine-endian form, return the first unicode codepoint. The pointer will be incremented to point at the next codepoint's start. If invalid UTF-32 is encountered, set *ptr to end and return -1. */ SK_SPI SkUnichar NextUTF32(const int32_t** ptr, const int32_t* end); constexpr unsigned kMaxBytesInUTF8Sequence = 4; /** Convert the unicode codepoint into UTF-8. If `utf8` is non-null, place the result in that array. Return the number of bytes in the result. If `utf8` is null, simply return the number of bytes that would be used. For invalid unicode codepoints, return 0. */ SK_SPI size_t ToUTF8(SkUnichar uni, char utf8[kMaxBytesInUTF8Sequence] = nullptr); /** Convert the unicode codepoint into UTF-16. If `utf16` is non-null, place the result in that array. Return the number of UTF-16 code units in the result (1 or 2). If `utf16` is null, simply return the number of code units that would be used. For invalid unicode codepoints, return 0. */ SK_SPI size_t ToUTF16(SkUnichar uni, uint16_t utf16[2] = nullptr); /** Returns the number of resulting UTF16 values needed to convert the src utf8 sequence. * If dst is not null, it is filled with the corresponding values up to its capacity. * If there is an error, -1 is returned and the dst[] buffer is undefined. */ SK_SPI int UTF8ToUTF16(uint16_t dst[], int dstCapacity, const char src[], size_t srcByteLength); /** Returns the number of resulting UTF8 values needed to convert the src utf16 sequence. * If dst is not null, it is filled with the corresponding values up to its capacity. * If there is an error, -1 is returned and the dst[] buffer is undefined. */ SK_SPI int UTF16ToUTF8(char dst[], int dstCapacity, const uint16_t src[], size_t srcLength); /** * Given a UTF-16 code point, returns true iff it is a leading surrogate. * https://unicode.org/faq/utf_bom.html#utf16-2 */ static inline bool IsLeadingSurrogateUTF16(uint16_t c) { return ((c) & 0xFC00) == 0xD800; } /** * Given a UTF-16 code point, returns true iff it is a trailing surrogate. * https://unicode.org/faq/utf_bom.html#utf16-2 */ static inline bool IsTrailingSurrogateUTF16(uint16_t c) { return ((c) & 0xFC00) == 0xDC00; } } // namespace SkUTF #endif // SkUTF_DEFINED namespace SkHexadecimalDigits { extern const char gUpper[16]; // 0-9A-F extern const char gLower[16]; // 0-9a-f } // namespace SkHexadecimalDigits /////////////////////////////////////////////////////////////////////////////// // If T is an 8-byte GCC or Clang vector extension type, it would naturally // pass or return in the MMX mm0 register on 32-bit x86 builds. This has the // fun side effect of clobbering any state in the x87 st0 register. (There is // no ABI governing who should preserve mm?/st? registers, so no one does!) // // We force-inline sk_unaligned_load() and sk_unaligned_store() to avoid that, // making them safe to use for all types on all platforms, thus solving the // problem once and for all! template static SK_ALWAYS_INLINE T sk_unaligned_load(const P* ptr) { static_assert(std::is_trivially_copyable::value); T val; memcpy(&val, ptr, sizeof(val)); return val; } template static SK_ALWAYS_INLINE void sk_unaligned_store(P* ptr, T val) { static_assert(std::is_trivially_copyable::value); memcpy(ptr, &val, sizeof(val)); } // Copy the bytes from src into an instance of type Dst and return it. template static SK_ALWAYS_INLINE Dst sk_bit_cast(const Src& src) { static_assert(sizeof(Dst) == sizeof(Src)); static_assert(std::is_trivially_copyable::value); static_assert(std::is_trivially_copyable::value); return sk_unaligned_load(&src); } #ifndef SKVX_DEFINED #define SKVX_DEFINED // skvx::Vec are SIMD vectors of N T's, a v1.5 successor to SkNx. // // This time we're leaning a bit less on platform-specific intrinsics and a bit // more on Clang/GCC vector extensions, but still keeping the option open to // drop in platform-specific intrinsics, actually more easily than before. // // We've also fixed a few of the caveats that used to make SkNx awkward to work // with across translation units. skvx::Vec always has N*sizeof(T) size // and alignment and is safe to use across translation units freely. // (Ideally we'd only align to T, but that tanks ARMv7 NEON codegen.) // Users may disable SIMD with SKNX_NO_SIMD, which may be set via compiler flags. // The gn build has no option which sets SKNX_NO_SIMD. // Use SKVX_USE_SIMD internally to avoid confusing double negation. // Do not use 'defined' in a macro expansion. #if !defined(SKNX_NO_SIMD) #define SKVX_USE_SIMD 1 #else #define SKVX_USE_SIMD 0 #endif #if SKVX_USE_SIMD #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 #elif defined(SK_ARM_HAS_NEON) #elif defined(__wasm_simd128__) #endif #endif // To avoid ODR violations, all methods must be force-inlined... #if defined(_MSC_VER) #define SKVX_ALWAYS_INLINE __forceinline #else #define SKVX_ALWAYS_INLINE __attribute__((always_inline)) #endif // ... and all standalone functions must be static. Please use these helpers: #define SI static inline #define SIT template < typename T> SI #define SIN template SI #define SINT template SI #define SINTU template ::value>> SI namespace skvx { template struct alignas(N*sizeof(T)) Vec; template SI Vec shuffle(const Vec&); // All Vec have the same simple memory layout, the same as `T vec[N]`. template struct alignas(N*sizeof(T)) Vec { static_assert((N & (N-1)) == 0, "N must be a power of 2."); static_assert(sizeof(T) >= alignof(T), "What kind of unusual T is this?"); // Methods belong here in the class declaration of Vec only if: // - they must be here, like constructors or operator[]; // - they'll definitely never want a specialized implementation. // Other operations on Vec should be defined outside the type. SKVX_ALWAYS_INLINE Vec() = default; SKVX_ALWAYS_INLINE Vec(T s) : lo(s), hi(s) {} // NOTE: Vec{x} produces x000..., whereas Vec(x) produces xxxx.... since this constructor fills // unspecified lanes with 0s, whereas the single T constructor fills all lanes with the value. SKVX_ALWAYS_INLINE Vec(std::initializer_list xs) { T vals[N] = {0}; assert(xs.size() <= (size_t)N); memcpy(vals, xs.begin(), std::min(xs.size(), (size_t)N)*sizeof(T)); this->lo = Vec::Load(vals + 0); this->hi = Vec::Load(vals + N/2); } SKVX_ALWAYS_INLINE T operator[](int i) const { return ilo[i] : this->hi[i-N/2]; } SKVX_ALWAYS_INLINE T& operator[](int i) { return ilo[i] : this->hi[i-N/2]; } SKVX_ALWAYS_INLINE static Vec Load(const void* ptr) { return sk_unaligned_load(ptr); } SKVX_ALWAYS_INLINE void store(void* ptr) const { // Note: Calling sk_unaligned_store produces slightly worse code here, for some reason memcpy(ptr, this, sizeof(Vec)); } Vec lo, hi; }; // We have specializations for N == 1 (the base-case), as well as 2 and 4, where we add helpful // constructors and swizzle accessors. template struct alignas(4*sizeof(T)) Vec<4,T> { static_assert(sizeof(T) >= alignof(T), "What kind of unusual T is this?"); SKVX_ALWAYS_INLINE Vec() = default; SKVX_ALWAYS_INLINE Vec(T s) : lo(s), hi(s) {} SKVX_ALWAYS_INLINE Vec(T x, T y, T z, T w) : lo(x,y), hi(z,w) {} SKVX_ALWAYS_INLINE Vec(Vec<2,T> xy, T z, T w) : lo(xy), hi(z,w) {} SKVX_ALWAYS_INLINE Vec(T x, T y, Vec<2,T> zw) : lo(x,y), hi(zw) {} SKVX_ALWAYS_INLINE Vec(Vec<2,T> xy, Vec<2,T> zw) : lo(xy), hi(zw) {} SKVX_ALWAYS_INLINE Vec(std::initializer_list xs) { T vals[4] = {0}; assert(xs.size() <= (size_t)4); memcpy(vals, xs.begin(), std::min(xs.size(), (size_t)4)*sizeof(T)); this->lo = Vec<2,T>::Load(vals + 0); this->hi = Vec<2,T>::Load(vals + 2); } SKVX_ALWAYS_INLINE T operator[](int i) const { return i<2 ? this->lo[i] : this->hi[i-2]; } SKVX_ALWAYS_INLINE T& operator[](int i) { return i<2 ? this->lo[i] : this->hi[i-2]; } SKVX_ALWAYS_INLINE static Vec Load(const void* ptr) { return sk_unaligned_load(ptr); } SKVX_ALWAYS_INLINE void store(void* ptr) const { memcpy(ptr, this, sizeof(Vec)); } SKVX_ALWAYS_INLINE Vec<2,T>& xy() { return lo; } SKVX_ALWAYS_INLINE Vec<2,T>& zw() { return hi; } SKVX_ALWAYS_INLINE T& x() { return lo.lo.val; } SKVX_ALWAYS_INLINE T& y() { return lo.hi.val; } SKVX_ALWAYS_INLINE T& z() { return hi.lo.val; } SKVX_ALWAYS_INLINE T& w() { return hi.hi.val; } SKVX_ALWAYS_INLINE Vec<2,T> xy() const { return lo; } SKVX_ALWAYS_INLINE Vec<2,T> zw() const { return hi; } SKVX_ALWAYS_INLINE T x() const { return lo.lo.val; } SKVX_ALWAYS_INLINE T y() const { return lo.hi.val; } SKVX_ALWAYS_INLINE T z() const { return hi.lo.val; } SKVX_ALWAYS_INLINE T w() const { return hi.hi.val; } // Exchange-based swizzles. These should take 1 cycle on NEON and 3 (pipelined) cycles on SSE. SKVX_ALWAYS_INLINE Vec<4,T> yxwz() const { return shuffle<1,0,3,2>(*this); } SKVX_ALWAYS_INLINE Vec<4,T> zwxy() const { return shuffle<2,3,0,1>(*this); } Vec<2,T> lo, hi; }; template struct alignas(2*sizeof(T)) Vec<2,T> { static_assert(sizeof(T) >= alignof(T), "What kind of unusual T is this?"); SKVX_ALWAYS_INLINE Vec() = default; SKVX_ALWAYS_INLINE Vec(T s) : lo(s), hi(s) {} SKVX_ALWAYS_INLINE Vec(T x, T y) : lo(x), hi(y) {} SKVX_ALWAYS_INLINE Vec(std::initializer_list xs) { T vals[2] = {0}; assert(xs.size() <= (size_t)2); memcpy(vals, xs.begin(), std::min(xs.size(), (size_t)2)*sizeof(T)); this->lo = Vec<1,T>::Load(vals + 0); this->hi = Vec<1,T>::Load(vals + 1); } SKVX_ALWAYS_INLINE T operator[](int i) const { return i<1 ? this->lo[i] : this->hi[i-1]; } SKVX_ALWAYS_INLINE T& operator[](int i) { return i<1 ? this->lo[i] : this->hi[i-1]; } SKVX_ALWAYS_INLINE static Vec Load(const void* ptr) { return sk_unaligned_load(ptr); } SKVX_ALWAYS_INLINE void store(void* ptr) const { memcpy(ptr, this, sizeof(Vec)); } SKVX_ALWAYS_INLINE T& x() { return lo.val; } SKVX_ALWAYS_INLINE T& y() { return hi.val; } SKVX_ALWAYS_INLINE T x() const { return lo.val; } SKVX_ALWAYS_INLINE T y() const { return hi.val; } // This exchange-based swizzle should take 1 cycle on NEON and 3 (pipelined) cycles on SSE. SKVX_ALWAYS_INLINE Vec<2,T> yx() const { return shuffle<1,0>(*this); } SKVX_ALWAYS_INLINE Vec<4,T> xyxy() const { return Vec<4,T>(*this, *this); } Vec<1,T> lo, hi; }; template struct Vec<1,T> { T val; SKVX_ALWAYS_INLINE Vec() = default; SKVX_ALWAYS_INLINE Vec(T s) : val(s) {} SKVX_ALWAYS_INLINE Vec(std::initializer_list xs) : val(xs.size() ? *xs.begin() : 0) { assert(xs.size() <= (size_t)1); } SKVX_ALWAYS_INLINE T operator[](int i) const { assert(i == 0); return val; } SKVX_ALWAYS_INLINE T& operator[](int i) { assert(i == 0); return val; } SKVX_ALWAYS_INLINE static Vec Load(const void* ptr) { return sk_unaligned_load(ptr); } SKVX_ALWAYS_INLINE void store(void* ptr) const { memcpy(ptr, this, sizeof(Vec)); } }; // Translate from a value type T to its corresponding Mask, the result of a comparison. template struct Mask { using type = T; }; template <> struct Mask { using type = int32_t; }; template <> struct Mask { using type = int64_t; }; template using M = typename Mask::type; // Join two Vec into one Vec<2N,T>. SINT Vec<2*N,T> join(const Vec& lo, const Vec& hi) { Vec<2*N,T> v; v.lo = lo; v.hi = hi; return v; } // We have three strategies for implementing Vec operations: // 1) lean on Clang/GCC vector extensions when available; // 2) use map() to apply a scalar function lane-wise; // 3) recurse on lo/hi to scalar portable implementations. // We can slot in platform-specific implementations as overloads for particular Vec, // or often integrate them directly into the recursion of style 3), allowing fine control. #if SKVX_USE_SIMD && (defined(__clang__) || defined(__GNUC__)) // VExt types have the same size as Vec and support most operations directly. #if defined(__clang__) template using VExt = T __attribute__((ext_vector_type(N))); #elif defined(__GNUC__) template struct VExtHelper { typedef T __attribute__((vector_size(N*sizeof(T)))) type; }; template using VExt = typename VExtHelper::type; // For some reason some (new!) versions of GCC cannot seem to deduce N in the generic // to_vec() below for N=4 and T=float. This workaround seems to help... SI Vec<4,float> to_vec(VExt<4,float> v) { return sk_bit_cast>(v); } #endif SINT VExt to_vext(const Vec& v) { return sk_bit_cast>(v); } SINT Vec to_vec(const VExt& v) { return sk_bit_cast>(v); } SINT Vec operator+(const Vec& x, const Vec& y) { return to_vec(to_vext(x) + to_vext(y)); } SINT Vec operator-(const Vec& x, const Vec& y) { return to_vec(to_vext(x) - to_vext(y)); } SINT Vec operator*(const Vec& x, const Vec& y) { return to_vec(to_vext(x) * to_vext(y)); } SINT Vec operator/(const Vec& x, const Vec& y) { return to_vec(to_vext(x) / to_vext(y)); } SINT Vec operator^(const Vec& x, const Vec& y) { return to_vec(to_vext(x) ^ to_vext(y)); } SINT Vec operator&(const Vec& x, const Vec& y) { return to_vec(to_vext(x) & to_vext(y)); } SINT Vec operator|(const Vec& x, const Vec& y) { return to_vec(to_vext(x) | to_vext(y)); } SINT Vec operator!(const Vec& x) { return to_vec(!to_vext(x)); } SINT Vec operator-(const Vec& x) { return to_vec(-to_vext(x)); } SINT Vec operator~(const Vec& x) { return to_vec(~to_vext(x)); } SINT Vec operator<<(const Vec& x, int k) { return to_vec(to_vext(x) << k); } SINT Vec operator>>(const Vec& x, int k) { return to_vec(to_vext(x) >> k); } SINT Vec> operator==(const Vec& x, const Vec& y) { return sk_bit_cast>>(to_vext(x) == to_vext(y)); } SINT Vec> operator!=(const Vec& x, const Vec& y) { return sk_bit_cast>>(to_vext(x) != to_vext(y)); } SINT Vec> operator<=(const Vec& x, const Vec& y) { return sk_bit_cast>>(to_vext(x) <= to_vext(y)); } SINT Vec> operator>=(const Vec& x, const Vec& y) { return sk_bit_cast>>(to_vext(x) >= to_vext(y)); } SINT Vec> operator< (const Vec& x, const Vec& y) { return sk_bit_cast>>(to_vext(x) < to_vext(y)); } SINT Vec> operator> (const Vec& x, const Vec& y) { return sk_bit_cast>>(to_vext(x) > to_vext(y)); } #else // Either SKNX_NO_SIMD is defined, or Clang/GCC vector extensions are not available. // We'll implement things portably with N==1 scalar implementations and recursion onto them. // N == 1 scalar implementations. SIT Vec<1,T> operator+(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val + y.val; } SIT Vec<1,T> operator-(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val - y.val; } SIT Vec<1,T> operator*(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val * y.val; } SIT Vec<1,T> operator/(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val / y.val; } SIT Vec<1,T> operator^(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val ^ y.val; } SIT Vec<1,T> operator&(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val & y.val; } SIT Vec<1,T> operator|(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val | y.val; } SIT Vec<1,T> operator!(const Vec<1,T>& x) { return !x.val; } SIT Vec<1,T> operator-(const Vec<1,T>& x) { return -x.val; } SIT Vec<1,T> operator~(const Vec<1,T>& x) { return ~x.val; } SIT Vec<1,T> operator<<(const Vec<1,T>& x, int k) { return x.val << k; } SIT Vec<1,T> operator>>(const Vec<1,T>& x, int k) { return x.val >> k; } SIT Vec<1,M> operator==(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val == y.val ? ~0 : 0; } SIT Vec<1,M> operator!=(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val != y.val ? ~0 : 0; } SIT Vec<1,M> operator<=(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val <= y.val ? ~0 : 0; } SIT Vec<1,M> operator>=(const Vec<1,T>& x, const Vec<1,T>& y) { return x.val >= y.val ? ~0 : 0; } SIT Vec<1,M> operator< (const Vec<1,T>& x, const Vec<1,T>& y) { return x.val < y.val ? ~0 : 0; } SIT Vec<1,M> operator> (const Vec<1,T>& x, const Vec<1,T>& y) { return x.val > y.val ? ~0 : 0; } // Recurse on lo/hi down to N==1 scalar implementations. SINT Vec operator+(const Vec& x, const Vec& y) { return join(x.lo + y.lo, x.hi + y.hi); } SINT Vec operator-(const Vec& x, const Vec& y) { return join(x.lo - y.lo, x.hi - y.hi); } SINT Vec operator*(const Vec& x, const Vec& y) { return join(x.lo * y.lo, x.hi * y.hi); } SINT Vec operator/(const Vec& x, const Vec& y) { return join(x.lo / y.lo, x.hi / y.hi); } SINT Vec operator^(const Vec& x, const Vec& y) { return join(x.lo ^ y.lo, x.hi ^ y.hi); } SINT Vec operator&(const Vec& x, const Vec& y) { return join(x.lo & y.lo, x.hi & y.hi); } SINT Vec operator|(const Vec& x, const Vec& y) { return join(x.lo | y.lo, x.hi | y.hi); } SINT Vec operator!(const Vec& x) { return join(!x.lo, !x.hi); } SINT Vec operator-(const Vec& x) { return join(-x.lo, -x.hi); } SINT Vec operator~(const Vec& x) { return join(~x.lo, ~x.hi); } SINT Vec operator<<(const Vec& x, int k) { return join(x.lo << k, x.hi << k); } SINT Vec operator>>(const Vec& x, int k) { return join(x.lo >> k, x.hi >> k); } SINT Vec> operator==(const Vec& x, const Vec& y) { return join(x.lo == y.lo, x.hi == y.hi); } SINT Vec> operator!=(const Vec& x, const Vec& y) { return join(x.lo != y.lo, x.hi != y.hi); } SINT Vec> operator<=(const Vec& x, const Vec& y) { return join(x.lo <= y.lo, x.hi <= y.hi); } SINT Vec> operator>=(const Vec& x, const Vec& y) { return join(x.lo >= y.lo, x.hi >= y.hi); } SINT Vec> operator< (const Vec& x, const Vec& y) { return join(x.lo < y.lo, x.hi < y.hi); } SINT Vec> operator> (const Vec& x, const Vec& y) { return join(x.lo > y.lo, x.hi > y.hi); } #endif // Scalar/vector operations splat the scalar to a vector. SINTU Vec operator+ (U x, const Vec& y) { return Vec(x) + y; } SINTU Vec operator- (U x, const Vec& y) { return Vec(x) - y; } SINTU Vec operator* (U x, const Vec& y) { return Vec(x) * y; } SINTU Vec operator/ (U x, const Vec& y) { return Vec(x) / y; } SINTU Vec operator^ (U x, const Vec& y) { return Vec(x) ^ y; } SINTU Vec operator& (U x, const Vec& y) { return Vec(x) & y; } SINTU Vec operator| (U x, const Vec& y) { return Vec(x) | y; } SINTU Vec> operator==(U x, const Vec& y) { return Vec(x) == y; } SINTU Vec> operator!=(U x, const Vec& y) { return Vec(x) != y; } SINTU Vec> operator<=(U x, const Vec& y) { return Vec(x) <= y; } SINTU Vec> operator>=(U x, const Vec& y) { return Vec(x) >= y; } SINTU Vec> operator< (U x, const Vec& y) { return Vec(x) < y; } SINTU Vec> operator> (U x, const Vec& y) { return Vec(x) > y; } SINTU Vec operator+ (const Vec& x, U y) { return x + Vec(y); } SINTU Vec operator- (const Vec& x, U y) { return x - Vec(y); } SINTU Vec operator* (const Vec& x, U y) { return x * Vec(y); } SINTU Vec operator/ (const Vec& x, U y) { return x / Vec(y); } SINTU Vec operator^ (const Vec& x, U y) { return x ^ Vec(y); } SINTU Vec operator& (const Vec& x, U y) { return x & Vec(y); } SINTU Vec operator| (const Vec& x, U y) { return x | Vec(y); } SINTU Vec> operator==(const Vec& x, U y) { return x == Vec(y); } SINTU Vec> operator!=(const Vec& x, U y) { return x != Vec(y); } SINTU Vec> operator<=(const Vec& x, U y) { return x <= Vec(y); } SINTU Vec> operator>=(const Vec& x, U y) { return x >= Vec(y); } SINTU Vec> operator< (const Vec& x, U y) { return x < Vec(y); } SINTU Vec> operator> (const Vec& x, U y) { return x > Vec(y); } SINT Vec& operator+=(Vec& x, const Vec& y) { return (x = x + y); } SINT Vec& operator-=(Vec& x, const Vec& y) { return (x = x - y); } SINT Vec& operator*=(Vec& x, const Vec& y) { return (x = x * y); } SINT Vec& operator/=(Vec& x, const Vec& y) { return (x = x / y); } SINT Vec& operator^=(Vec& x, const Vec& y) { return (x = x ^ y); } SINT Vec& operator&=(Vec& x, const Vec& y) { return (x = x & y); } SINT Vec& operator|=(Vec& x, const Vec& y) { return (x = x | y); } SINTU Vec& operator+=(Vec& x, U y) { return (x = x + Vec(y)); } SINTU Vec& operator-=(Vec& x, U y) { return (x = x - Vec(y)); } SINTU Vec& operator*=(Vec& x, U y) { return (x = x * Vec(y)); } SINTU Vec& operator/=(Vec& x, U y) { return (x = x / Vec(y)); } SINTU Vec& operator^=(Vec& x, U y) { return (x = x ^ Vec(y)); } SINTU Vec& operator&=(Vec& x, U y) { return (x = x & Vec(y)); } SINTU Vec& operator|=(Vec& x, U y) { return (x = x | Vec(y)); } SINT Vec& operator<<=(Vec& x, int bits) { return (x = x << bits); } SINT Vec& operator>>=(Vec& x, int bits) { return (x = x >> bits); } // Some operations we want are not expressible with Clang/GCC vector extensions. // Clang can reason about naive_if_then_else() and optimize through it better // than if_then_else(), so it's sometimes useful to call it directly when we // think an entire expression should optimize away, e.g. min()/max(). SINT Vec naive_if_then_else(const Vec>& cond, const Vec& t, const Vec& e) { return sk_bit_cast>(( cond & sk_bit_cast>>(t)) | (~cond & sk_bit_cast>>(e)) ); } SIT Vec<1,T> if_then_else(const Vec<1,M>& cond, const Vec<1,T>& t, const Vec<1,T>& e) { // In practice this scalar implementation is unlikely to be used. See next if_then_else(). return sk_bit_cast>(( cond & sk_bit_cast>>(t)) | (~cond & sk_bit_cast>>(e)) ); } SINT Vec if_then_else(const Vec>& cond, const Vec& t, const Vec& e) { // Specializations inline here so they can generalize what types the apply to. #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 if constexpr (N*sizeof(T) == 32) { return sk_bit_cast>(_mm256_blendv_epi8(sk_bit_cast<__m256i>(e), sk_bit_cast<__m256i>(t), sk_bit_cast<__m256i>(cond))); } #endif #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 if constexpr (N*sizeof(T) == 16) { return sk_bit_cast>(_mm_blendv_epi8(sk_bit_cast<__m128i>(e), sk_bit_cast<__m128i>(t), sk_bit_cast<__m128i>(cond))); } #endif #if SKVX_USE_SIMD && defined(SK_ARM_HAS_NEON) if constexpr (N*sizeof(T) == 16) { return sk_bit_cast>(vbslq_u8(sk_bit_cast(cond), sk_bit_cast(t), sk_bit_cast(e))); } #endif // Recurse for large vectors to try to hit the specializations above. if constexpr (N*sizeof(T) > 16) { return join(if_then_else(cond.lo, t.lo, e.lo), if_then_else(cond.hi, t.hi, e.hi)); } // This default can lead to better code than the recursing onto scalars. return naive_if_then_else(cond, t, e); } SIT bool any(const Vec<1,T>& x) { return x.val != 0; } SINT bool any(const Vec& x) { // For any(), the _mm_testz intrinsics are correct and don't require comparing 'x' to 0, so it's // lower latency compared to _mm_movemask + _mm_compneq on plain SSE. #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX2 if constexpr (N*sizeof(T) == 32) { return !_mm256_testz_si256(sk_bit_cast<__m256i>(x), _mm256_set1_epi32(-1)); } #endif #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE41 if constexpr (N*sizeof(T) == 16) { return !_mm_testz_si128(sk_bit_cast<__m128i>(x), _mm_set1_epi32(-1)); } #endif #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 if constexpr (N*sizeof(T) == 16) { // On SSE, movemask checks only the MSB in each lane, which is fine if the lanes were set // directly from a comparison op (which sets all bits to 1 when true), but skvx::Vec<> // treats any non-zero value as true, so we have to compare 'x' to 0 before calling movemask return _mm_movemask_ps(_mm_cmpneq_ps(sk_bit_cast<__m128>(x), _mm_set1_ps(0))) != 0b0000; } #endif #if SKVX_USE_SIMD && defined(__aarch64__) // On 64-bit NEON, take the max across lanes, which will be non-zero if any lane was true. // The specific lane-size doesn't really matter in this case since it's really any set bit // that we're looking for. if constexpr (N*sizeof(T) == 8 ) { return vmaxv_u8 (sk_bit_cast (x)) > 0; } if constexpr (N*sizeof(T) == 16) { return vmaxvq_u8(sk_bit_cast(x)) > 0; } #endif #if SKVX_USE_SIMD && defined(__wasm_simd128__) if constexpr (N == 4 && sizeof(T) == 4) { return wasm_i32x4_any_true(sk_bit_cast>(x)); } #endif return any(x.lo) || any(x.hi); } SIT bool all(const Vec<1,T>& x) { return x.val != 0; } SINT bool all(const Vec& x) { // Unlike any(), we have to respect the lane layout, or we'll miss cases where a // true lane has a mix of 0 and 1 bits. #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 // Unfortunately, the _mm_testc intrinsics don't let us avoid the comparison to 0 for all()'s // correctness, so always just use the plain SSE version. if constexpr (N == 4 && sizeof(T) == 4) { return _mm_movemask_ps(_mm_cmpneq_ps(sk_bit_cast<__m128>(x), _mm_set1_ps(0))) == 0b1111; } #endif #if SKVX_USE_SIMD && defined(__aarch64__) // On 64-bit NEON, take the min across the lanes, which will be non-zero if all lanes are != 0. if constexpr (sizeof(T)==1 && N==8) {return vminv_u8 (sk_bit_cast (x)) > 0;} if constexpr (sizeof(T)==1 && N==16) {return vminvq_u8 (sk_bit_cast(x)) > 0;} if constexpr (sizeof(T)==2 && N==4) {return vminv_u16 (sk_bit_cast(x)) > 0;} if constexpr (sizeof(T)==2 && N==8) {return vminvq_u16(sk_bit_cast(x)) > 0;} if constexpr (sizeof(T)==4 && N==2) {return vminv_u32 (sk_bit_cast(x)) > 0;} if constexpr (sizeof(T)==4 && N==4) {return vminvq_u32(sk_bit_cast(x)) > 0;} #endif #if SKVX_USE_SIMD && defined(__wasm_simd128__) if constexpr (N == 4 && sizeof(T) == 4) { return wasm_i32x4_all_true(sk_bit_cast>(x)); } #endif return all(x.lo) && all(x.hi); } // cast() Vec to Vec, as if applying a C-cast to each lane. // TODO: implement with map()? template SI Vec<1,D> cast(const Vec<1,S>& src) { return (D)src.val; } template SI Vec cast(const Vec& src) { #if SKVX_USE_SIMD && defined(__clang__) return to_vec(__builtin_convertvector(to_vext(src), VExt)); #else return join(cast(src.lo), cast(src.hi)); #endif } // min/max match logic of std::min/std::max, which is important when NaN is involved. SIT T min(const Vec<1,T>& x) { return x.val; } SIT T max(const Vec<1,T>& x) { return x.val; } SINT T min(const Vec& x) { return std::min(min(x.lo), min(x.hi)); } SINT T max(const Vec& x) { return std::max(max(x.lo), max(x.hi)); } SINT Vec min(const Vec& x, const Vec& y) { return naive_if_then_else(y < x, y, x); } SINT Vec max(const Vec& x, const Vec& y) { return naive_if_then_else(x < y, y, x); } SINTU Vec min(const Vec& x, U y) { return min(x, Vec(y)); } SINTU Vec max(const Vec& x, U y) { return max(x, Vec(y)); } SINTU Vec min(U x, const Vec& y) { return min(Vec(x), y); } SINTU Vec max(U x, const Vec& y) { return max(Vec(x), y); } // pin matches the logic of SkTPin, which is important when NaN is involved. It always returns // values in the range lo..hi, and if x is NaN, it returns lo. SINT Vec pin(const Vec& x, const Vec& lo, const Vec& hi) { return max(lo, min(x, hi)); } // Shuffle values from a vector pretty arbitrarily: // skvx::Vec<4,float> rgba = {R,G,B,A}; // shuffle<2,1,0,3> (rgba) ~> {B,G,R,A} // shuffle<2,1> (rgba) ~> {B,G} // shuffle<2,1,2,1,2,1,2,1>(rgba) ~> {B,G,B,G,B,G,B,G} // shuffle<3,3,3,3> (rgba) ~> {A,A,A,A} // The only real restriction is that the output also be a legal N=power-of-two sknx::Vec. template SI Vec shuffle(const Vec& x) { #if SKVX_USE_SIMD && defined(__clang__) // TODO: can we just always use { x[Ix]... }? return to_vec(__builtin_shufflevector(to_vext(x), to_vext(x), Ix...)); #else return { x[Ix]... }; #endif } // Call map(fn, x) for a vector with fn() applied to each lane of x, { fn(x[0]), fn(x[1]), ... }, // or map(fn, x,y) for a vector of fn(x[i], y[i]), etc. template SI auto map(std::index_sequence, Fn&& fn, const Args&... args) -> skvx::Vec { auto lane = [&](size_t i) #if defined(__clang__) // CFI, specifically -fsanitize=cfi-icall, seems to give a false positive here, // with errors like "control flow integrity check for type 'float (float) // noexcept' failed during indirect function call... note: sqrtf.cfi_jt defined // here". But we can be quite sure fn is the right type: it's all inferred! // So, stifle CFI in this function. __attribute__((no_sanitize("cfi"))) #endif { return fn(args[static_cast(i)]...); }; return { lane(I)... }; } template auto map(Fn&& fn, const Vec& first, const Rest&... rest) { // Derive an {0...N-1} index_sequence from the size of the first arg: N lanes in, N lanes out. return map(std::make_index_sequence{}, fn, first,rest...); } SIN Vec ceil(const Vec& x) { return map( ceilf, x); } SIN Vec floor(const Vec& x) { return map(floorf, x); } SIN Vec trunc(const Vec& x) { return map(truncf, x); } SIN Vec round(const Vec& x) { return map(roundf, x); } SIN Vec sqrt(const Vec& x) { return map( sqrtf, x); } SIN Vec abs(const Vec& x) { return map( fabsf, x); } SIN Vec fma(const Vec& x, const Vec& y, const Vec& z) { // I don't understand why Clang's codegen is terrible if we write map(fmaf, x,y,z) directly. auto fn = [](float x, float y, float z) { return fmaf(x,y,z); }; return map(fn, x,y,z); } SI Vec<1,int> lrint(const Vec<1,float>& x) { return (int)lrintf(x.val); } SIN Vec lrint(const Vec& x) { #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX if constexpr (N == 8) { return sk_bit_cast>(_mm256_cvtps_epi32(sk_bit_cast<__m256>(x))); } #endif #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 if constexpr (N == 4) { return sk_bit_cast>(_mm_cvtps_epi32(sk_bit_cast<__m128>(x))); } #endif return join(lrint(x.lo), lrint(x.hi)); } SIN Vec fract(const Vec& x) { return x - floor(x); } // Assumes inputs are finite and treat/flush denorm half floats as/to zero. // Key constants to watch for: // - a float is 32-bit, 1-8-23 sign-exponent-mantissa, with 127 exponent bias; // - a half is 16-bit, 1-5-10 sign-exponent-mantissa, with 15 exponent bias. SIN Vec to_half_finite_ftz(const Vec& x) { Vec sem = sk_bit_cast>(x), s = sem & 0x8000'0000, em = sem ^ s, is_norm = em > 0x387f'd000, // halfway between largest f16 denorm and smallest norm norm = (em>>13) - ((127-15)<<10); return cast((s>>16) | (is_norm & norm)); } SIN Vec from_half_finite_ftz(const Vec& x) { Vec wide = cast(x), s = wide & 0x8000, em = wide ^ s, is_norm = em > 0x3ff, norm = (em<<13) + ((127-15)<<23); return sk_bit_cast>((s<<16) | (is_norm & norm)); } // Like if_then_else(), these N=1 base cases won't actually be used unless explicitly called. SI Vec<1,uint16_t> to_half(const Vec<1,float>& x) { return to_half_finite_ftz(x); } SI Vec<1,float> from_half(const Vec<1,uint16_t>& x) { return from_half_finite_ftz(x); } SIN Vec to_half(const Vec& x) { #if SKVX_USE_SIMD && defined(__aarch64__) if constexpr (N == 4) { return sk_bit_cast>(vcvt_f16_f32(sk_bit_cast(x))); } #endif if constexpr (N > 4) { return join(to_half(x.lo), to_half(x.hi)); } return to_half_finite_ftz(x); } SIN Vec from_half(const Vec& x) { #if SKVX_USE_SIMD && defined(__aarch64__) if constexpr (N == 4) { return sk_bit_cast>(vcvt_f32_f16(sk_bit_cast(x))); } #endif if constexpr (N > 4) { return join(from_half(x.lo), from_half(x.hi)); } return from_half_finite_ftz(x); } // div255(x) = (x + 127) / 255 is a bit-exact rounding divide-by-255, packing down to 8-bit. SIN Vec div255(const Vec& x) { return cast( (x+127)/255 ); } // approx_scale(x,y) approximates div255(cast(x)*cast(y)) within a bit, // and is always perfect when x or y is 0 or 255. SIN Vec approx_scale(const Vec& x, const Vec& y) { // All of (x*y+x)/256, (x*y+y)/256, and (x*y+255)/256 meet the criteria above. // We happen to have historically picked (x*y+x)/256. auto X = cast(x), Y = cast(y); return cast( (X*Y+X)/256 ); } // saturated_add(x,y) sums values and clamps to the maximum value instead of overflowing. SINT std::enable_if_t, Vec> saturated_add(const Vec& x, const Vec& y) { #if SKVX_USE_SIMD && (SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 || defined(SK_ARM_HAS_NEON)) // Both SSE and ARM have 16-lane saturated adds, so use intrinsics for those and recurse down // or join up to take advantage. if constexpr (N == 16 && sizeof(T) == 1) { #if SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 return sk_bit_cast>(_mm_adds_epu8(sk_bit_cast<__m128i>(x), sk_bit_cast<__m128i>(y))); #else // SK_ARM_HAS_NEON return sk_bit_cast>(vqaddq_u8(sk_bit_cast(x), sk_bit_cast(y))); #endif } else if constexpr (N < 16 && sizeof(T) == 1) { return saturated_add(join(x,x), join(y,y)).lo; } else if constexpr (sizeof(T) == 1) { return join(saturated_add(x.lo, y.lo), saturated_add(x.hi, y.hi)); } #endif // Otherwise saturate manually auto sum = x + y; return if_then_else(sum < x, Vec(std::numeric_limits::max()), sum); } // The ScaledDividerU32 takes a divisor > 1, and creates a function divide(numerator) that // calculates a numerator / denominator. For this to be rounded properly, numerator should have // half added in: // divide(numerator + half) == floor(numerator/denominator + 1/2). // // This gives an answer within +/- 1 from the true value. // // Derivation of half: // numerator/denominator + 1/2 = (numerator + half) / d // numerator + denominator / 2 = numerator + half // half = denominator / 2. // // Because half is divided by 2, that division must also be rounded. // half == denominator / 2 = (denominator + 1) / 2. // // The divisorFactor is just a scaled value: // divisorFactor = (1 / divisor) * 2 ^ 32. // The maximum that can be divided and rounded is UINT_MAX - half. class ScaledDividerU32 { public: explicit ScaledDividerU32(uint32_t divisor) : fDivisorFactor{(uint32_t)(std::round((1.0 / divisor) * (1ull << 32)))} , fHalf{(divisor + 1) >> 1} { assert(divisor > 1); } Vec<4, uint32_t> divide(const Vec<4, uint32_t>& numerator) const { #if SKVX_USE_SIMD && defined(SK_ARM_HAS_NEON) uint64x2_t hi = vmull_n_u32(vget_high_u32(to_vext(numerator)), fDivisorFactor); uint64x2_t lo = vmull_n_u32(vget_low_u32(to_vext(numerator)), fDivisorFactor); return to_vec<4, uint32_t>(vcombine_u32(vshrn_n_u64(lo,32), vshrn_n_u64(hi,32))); #else return cast((cast(numerator) * fDivisorFactor) >> 32); #endif } uint32_t half() const { return fHalf; } private: const uint32_t fDivisorFactor; const uint32_t fHalf; }; SIN Vec mull(const Vec& x, const Vec& y) { #if SKVX_USE_SIMD && defined(SK_ARM_HAS_NEON) // With NEON we can do eight u8*u8 -> u16 in one instruction, vmull_u8 (read, mul-long). if constexpr (N == 8) { return to_vec<8,uint16_t>(vmull_u8(to_vext(x), to_vext(y))); } else if constexpr (N < 8) { return mull(join(x,x), join(y,y)).lo; } else { // N > 8 return join(mull(x.lo, y.lo), mull(x.hi, y.hi)); } #else return cast(x) * cast(y); #endif } SIN Vec mull(const Vec& x, const Vec& y) { #if SKVX_USE_SIMD && defined(SK_ARM_HAS_NEON) // NEON can do four u16*u16 -> u32 in one instruction, vmull_u16 if constexpr (N == 4) { return to_vec<4,uint32_t>(vmull_u16(to_vext(x), to_vext(y))); } else if constexpr (N < 4) { return mull(join(x,x), join(y,y)).lo; } else { // N > 4 return join(mull(x.lo, y.lo), mull(x.hi, y.hi)); } #else return cast(x) * cast(y); #endif } SIN Vec mulhi(const Vec& x, const Vec& y) { #if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 // Use _mm_mulhi_epu16 for 8xuint16_t and join or split to get there. if constexpr (N == 8) { return sk_bit_cast>(_mm_mulhi_epu16(sk_bit_cast<__m128i>(x), sk_bit_cast<__m128i>(y))); } else if constexpr (N < 8) { return mulhi(join(x,x), join(y,y)).lo; } else { // N > 8 return join(mulhi(x.lo, y.lo), mulhi(x.hi, y.hi)); } #else return skvx::cast(mull(x, y) >> 16); #endif } SINT T dot(const Vec& a, const Vec& b) { // While dot is a "horizontal" operation like any or all, it needs to remain // in floating point and there aren't really any good SIMD instructions that make it faster. // The constexpr cases remove the for loop in the only cases we realistically call. auto ab = a*b; if constexpr (N == 2) { return ab[0] + ab[1]; } else if constexpr (N == 4) { return ab[0] + ab[1] + ab[2] + ab[3]; } else { T sum = ab[0]; for (int i = 1; i < N; ++i) { sum += ab[i]; } return sum; } } SIT T cross(const Vec<2, T>& a, const Vec<2, T>& b) { auto x = a * shuffle<1,0>(b); return x[0] - x[1]; } SIN float length(const Vec& v) { return std::sqrt(dot(v, v)); } SIN double length(const Vec& v) { return std::sqrt(dot(v, v)); } SIN Vec normalize(const Vec& v) { return v / length(v); } SIN Vec normalize(const Vec& v) { return v / length(v); } SINT bool isfinite(const Vec& v) { // Multiply all values together with 0. If they were all finite, the output is // 0 (also finite). If any were not, we'll get nan. return std::isfinite(dot(v, Vec(0))); } // De-interleaving load of 4 vectors. // // WARNING: These are really only supported well on NEON. Consider restructuring your data before // resorting to these methods. SIT void strided_load4(const T* v, Vec<1,T>& a, Vec<1,T>& b, Vec<1,T>& c, Vec<1,T>& d) { a.val = v[0]; b.val = v[1]; c.val = v[2]; d.val = v[3]; } SINT void strided_load4(const T* v, Vec& a, Vec& b, Vec& c, Vec& d) { strided_load4(v, a.lo, b.lo, c.lo, d.lo); strided_load4(v + 4*(N/2), a.hi, b.hi, c.hi, d.hi); } #if SKVX_USE_SIMD && defined(SK_ARM_HAS_NEON) #define IMPL_LOAD4_TRANSPOSED(N, T, VLD) \ SI void strided_load4(const T* v, \ Vec& a, \ Vec& b, \ Vec& c, \ Vec& d) { \ auto mat = VLD(v); \ a = sk_bit_cast>(mat.val[0]); \ b = sk_bit_cast>(mat.val[1]); \ c = sk_bit_cast>(mat.val[2]); \ d = sk_bit_cast>(mat.val[3]); \ } IMPL_LOAD4_TRANSPOSED(2, uint32_t, vld4_u32) IMPL_LOAD4_TRANSPOSED(4, uint16_t, vld4_u16) IMPL_LOAD4_TRANSPOSED(8, uint8_t, vld4_u8) IMPL_LOAD4_TRANSPOSED(2, int32_t, vld4_s32) IMPL_LOAD4_TRANSPOSED(4, int16_t, vld4_s16) IMPL_LOAD4_TRANSPOSED(8, int8_t, vld4_s8) IMPL_LOAD4_TRANSPOSED(2, float, vld4_f32) IMPL_LOAD4_TRANSPOSED(4, uint32_t, vld4q_u32) IMPL_LOAD4_TRANSPOSED(8, uint16_t, vld4q_u16) IMPL_LOAD4_TRANSPOSED(16, uint8_t, vld4q_u8) IMPL_LOAD4_TRANSPOSED(4, int32_t, vld4q_s32) IMPL_LOAD4_TRANSPOSED(8, int16_t, vld4q_s16) IMPL_LOAD4_TRANSPOSED(16, int8_t, vld4q_s8) IMPL_LOAD4_TRANSPOSED(4, float, vld4q_f32) #undef IMPL_LOAD4_TRANSPOSED #elif SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_SSE1 SI void strided_load4(const float* v, Vec<4,float>& a, Vec<4,float>& b, Vec<4,float>& c, Vec<4,float>& d) { __m128 a_ = _mm_loadu_ps(v); __m128 b_ = _mm_loadu_ps(v+4); __m128 c_ = _mm_loadu_ps(v+8); __m128 d_ = _mm_loadu_ps(v+12); _MM_TRANSPOSE4_PS(a_, b_, c_, d_); a = sk_bit_cast>(a_); b = sk_bit_cast>(b_); c = sk_bit_cast>(c_); d = sk_bit_cast>(d_); } #endif // De-interleaving load of 2 vectors. // // WARNING: These are really only supported well on NEON. Consider restructuring your data before // resorting to these methods. SIT void strided_load2(const T* v, Vec<1,T>& a, Vec<1,T>& b) { a.val = v[0]; b.val = v[1]; } SINT void strided_load2(const T* v, Vec& a, Vec& b) { strided_load2(v, a.lo, b.lo); strided_load2(v + 2*(N/2), a.hi, b.hi); } #if SKVX_USE_SIMD && defined(SK_ARM_HAS_NEON) #define IMPL_LOAD2_TRANSPOSED(N, T, VLD) \ SI void strided_load2(const T* v, Vec& a, Vec& b) { \ auto mat = VLD(v); \ a = sk_bit_cast>(mat.val[0]); \ b = sk_bit_cast>(mat.val[1]); \ } IMPL_LOAD2_TRANSPOSED(2, uint32_t, vld2_u32) IMPL_LOAD2_TRANSPOSED(4, uint16_t, vld2_u16) IMPL_LOAD2_TRANSPOSED(8, uint8_t, vld2_u8) IMPL_LOAD2_TRANSPOSED(2, int32_t, vld2_s32) IMPL_LOAD2_TRANSPOSED(4, int16_t, vld2_s16) IMPL_LOAD2_TRANSPOSED(8, int8_t, vld2_s8) IMPL_LOAD2_TRANSPOSED(2, float, vld2_f32) IMPL_LOAD2_TRANSPOSED(4, uint32_t, vld2q_u32) IMPL_LOAD2_TRANSPOSED(8, uint16_t, vld2q_u16) IMPL_LOAD2_TRANSPOSED(16, uint8_t, vld2q_u8) IMPL_LOAD2_TRANSPOSED(4, int32_t, vld2q_s32) IMPL_LOAD2_TRANSPOSED(8, int16_t, vld2q_s16) IMPL_LOAD2_TRANSPOSED(16, int8_t, vld2q_s8) IMPL_LOAD2_TRANSPOSED(4, float, vld2q_f32) #undef IMPL_LOAD2_TRANSPOSED #endif // Define commonly used aliases using float2 = Vec< 2, float>; using float4 = Vec< 4, float>; using float8 = Vec< 8, float>; using double2 = Vec< 2, double>; using double4 = Vec< 4, double>; using double8 = Vec< 8, double>; using byte2 = Vec< 2, uint8_t>; using byte4 = Vec< 4, uint8_t>; using byte8 = Vec< 8, uint8_t>; using byte16 = Vec<16, uint8_t>; using int2 = Vec< 2, int32_t>; using int4 = Vec< 4, int32_t>; using int8 = Vec< 8, int32_t>; using uint2 = Vec< 2, uint32_t>; using uint4 = Vec< 4, uint32_t>; using uint8 = Vec< 8, uint32_t>; using long2 = Vec< 2, int64_t>; using long4 = Vec< 4, int64_t>; using long8 = Vec< 8, int64_t>; // Use with from_half and to_half to convert between floatX, and use these for storage. using half2 = Vec< 2, uint16_t>; using half4 = Vec< 4, uint16_t>; using half8 = Vec< 8, uint16_t>; } // namespace skvx #undef SINTU #undef SINT #undef SIN #undef SIT #undef SI #undef SKVX_ALWAYS_INLINE #undef SKVX_USE_SIMD #endif//SKVX_DEFINED struct SkPoint; /** This class is initialized with a clip rectangle, and then can be fed cubics, which must already be monotonic in Y. In the future, it might return a series of segments, allowing it to clip also in X, to ensure that all segments fit in a finite coordinate system. */ class SkCubicClipper { public: SkCubicClipper(); void setClip(const SkIRect& clip); [[nodiscard]] bool clipCubic(const SkPoint src[4], SkPoint dst[4]); [[nodiscard]] static bool ChopMonoAtY(const SkPoint pts[4], SkScalar y, SkScalar* t); private: SkRect fClip; }; class SkMatrix; struct SkRect; static inline skvx::float2 from_point(const SkPoint& point) { return skvx::float2::Load(&point); } static inline SkPoint to_point(const skvx::float2& x) { SkPoint point; x.store(&point); return point; } static skvx::float2 times_2(const skvx::float2& value) { return value + value; } /** Given a quadratic equation Ax^2 + Bx + C = 0, return 0, 1, 2 roots for the equation. */ int SkFindUnitQuadRoots(SkScalar A, SkScalar B, SkScalar C, SkScalar roots[2]); /** Measures the angle between two vectors, in the range [0, pi]. */ float SkMeasureAngleBetweenVectors(SkVector, SkVector); /** Returns a new, arbitrarily scaled vector that bisects the given vectors. The returned bisector will always point toward the interior of the provided vectors. */ SkVector SkFindBisector(SkVector, SkVector); /////////////////////////////////////////////////////////////////////////////// SkPoint SkEvalQuadAt(const SkPoint src[3], SkScalar t); SkPoint SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t); /** Set pt to the point on the src quadratic specified by t. t must be 0 <= t <= 1.0 */ void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tangent = nullptr); /** Given a src quadratic bezier, chop it at the specified t value, where 0 < t < 1, and return the two new quadratics in dst: dst[0..2] and dst[2..4] */ void SkChopQuadAt(const SkPoint src[3], SkPoint dst[5], SkScalar t); /** Given a src quadratic bezier, chop it at the specified t == 1/2, The new quads are returned in dst[0..2] and dst[2..4] */ void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]); /** Measures the rotation of the given quadratic curve in radians. Rotation is perhaps easiest described via a driving analogy: If you drive your car along the curve from p0 to p2, then by the time you arrive at p2, how many radians will your car have rotated? For a quadratic this is the same as the vector inside the tangents at the endpoints. Quadratics can have rotations in the range [0, pi]. */ inline float SkMeasureQuadRotation(const SkPoint pts[3]) { return SkMeasureAngleBetweenVectors(pts[1] - pts[0], pts[2] - pts[1]); } /** Given a src quadratic bezier, returns the T value whose tangent angle is halfway between the tangents at p0 and p3. */ float SkFindQuadMidTangent(const SkPoint src[3]); /** Given a src quadratic bezier, chop it at the tangent whose angle is halfway between the tangents at p0 and p2. The new quads are returned in dst[0..2] and dst[2..4]. */ inline void SkChopQuadAtMidTangent(const SkPoint src[3], SkPoint dst[5]) { SkChopQuadAt(src, dst, SkFindQuadMidTangent(src)); } /** Given the 3 coefficients for a quadratic bezier (either X or Y values), look for extrema, and return the number of t-values that are found that represent these extrema. If the quadratic has no extrema betwee (0..1) exclusive, the function returns 0. Returned count tValues[] 0 ignored 1 0 < tValues[0] < 1 */ int SkFindQuadExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar tValues[1]); /** Given 3 points on a quadratic bezier, chop it into 1, 2 beziers such that the resulting beziers are monotonic in Y. This is called by the scan converter. Depending on what is returned, dst[] is treated as follows 0 dst[0..2] is the original quad 1 dst[0..2] and dst[2..4] are the two new quads */ int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5]); int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5]); /** Given 3 points on a quadratic bezier, if the point of maximum curvature exists on the segment, returns the t value for this point along the curve. Otherwise it will return a value of 0. */ SkScalar SkFindQuadMaxCurvature(const SkPoint src[3]); /** Given 3 points on a quadratic bezier, divide it into 2 quadratics if the point of maximum curvature exists on the quad segment. Depending on what is returned, dst[] is treated as follows 1 dst[0..2] is the original quad 2 dst[0..2] and dst[2..4] are the two new quads If dst == null, it is ignored and only the count is returned. */ int SkChopQuadAtMaxCurvature(const SkPoint src[3], SkPoint dst[5]); /** Given 3 points on a quadratic bezier, use degree elevation to convert it into the cubic fitting the same curve. The new cubic curve is returned in dst[0..3]. */ void SkConvertQuadToCubic(const SkPoint src[3], SkPoint dst[4]); /////////////////////////////////////////////////////////////////////////////// /** Set pt to the point on the src cubic specified by t. t must be 0 <= t <= 1.0 */ void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint* locOrNull, SkVector* tangentOrNull, SkVector* curvatureOrNull); /** Given a src cubic bezier, chop it at the specified t value, where 0 <= t <= 1, and return the two new cubics in dst: dst[0..3] and dst[3..6] */ void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t); /** Given a src cubic bezier, chop it at the specified t0 and t1 values, where 0 <= t0 <= t1 <= 1, and return the three new cubics in dst: dst[0..3], dst[3..6], and dst[6..9] */ void SkChopCubicAt(const SkPoint src[4], SkPoint dst[10], float t0, float t1); /** Given a src cubic bezier, chop it at the specified t values, where 0 <= t0 <= t1 <= ... <= 1, and return the new cubics in dst: dst[0..3],dst[3..6],...,dst[3*t_count..3*(t_count+1)] */ void SkChopCubicAt(const SkPoint src[4], SkPoint dst[], const SkScalar t[], int t_count); /** Given a src cubic bezier, chop it at the specified t == 1/2, The new cubics are returned in dst[0..3] and dst[3..6] */ void SkChopCubicAtHalf(const SkPoint src[4], SkPoint dst[7]); /** Given a cubic curve with no inflection points, this method measures the rotation in radians. Rotation is perhaps easiest described via a driving analogy: If you drive your car along the curve from p0 to p3, then by the time you arrive at p3, how many radians will your car have rotated? This is not quite the same as the vector inside the tangents at the endpoints, even without inflection, because the curve might rotate around the outside of the tangents (>= 180 degrees) or the inside (<= 180 degrees). Cubics can have rotations in the range [0, 2*pi]. NOTE: The caller must either call SkChopCubicAtInflections or otherwise prove that the provided cubic has no inflection points prior to calling this method. */ float SkMeasureNonInflectCubicRotation(const SkPoint[4]); /** Given a src cubic bezier, returns the T value whose tangent angle is halfway between the tangents at p0 and p3. */ float SkFindCubicMidTangent(const SkPoint src[4]); /** Given a src cubic bezier, chop it at the tangent whose angle is halfway between the tangents at p0 and p3. The new cubics are returned in dst[0..3] and dst[3..6]. NOTE: 0- and 360-degree flat lines don't have single points of midtangent. (tangent == midtangent at every point on these curves except the cusp points.) If this is the case then we simply chop at a point which guarantees neither side rotates more than 180 degrees. */ inline void SkChopCubicAtMidTangent(const SkPoint src[4], SkPoint dst[7]) { SkChopCubicAt(src, dst, SkFindCubicMidTangent(src)); } /** Given the 4 coefficients for a cubic bezier (either X or Y values), look for extrema, and return the number of t-values that are found that represent these extrema. If the cubic has no extrema betwee (0..1) exclusive, the function returns 0. Returned count tValues[] 0 ignored 1 0 < tValues[0] < 1 2 0 < tValues[0] < tValues[1] < 1 */ int SkFindCubicExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar d, SkScalar tValues[2]); /** Given 4 points on a cubic bezier, chop it into 1, 2, 3 beziers such that the resulting beziers are monotonic in Y. This is called by the scan converter. Depending on what is returned, dst[] is treated as follows 0 dst[0..3] is the original cubic 1 dst[0..3] and dst[3..6] are the two new cubics 2 dst[0..3], dst[3..6], dst[6..9] are the three new cubics If dst == null, it is ignored and only the count is returned. */ int SkChopCubicAtYExtrema(const SkPoint src[4], SkPoint dst[10]); int SkChopCubicAtXExtrema(const SkPoint src[4], SkPoint dst[10]); /** Given a cubic bezier, return 0, 1, or 2 t-values that represent the inflection points. */ int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[2]); /** Return 1 for no chop, 2 for having chopped the cubic at a single inflection point, 3 for having chopped at 2 inflection points. dst will hold the resulting 1, 2, or 3 cubics. */ int SkChopCubicAtInflections(const SkPoint src[4], SkPoint dst[10]); int SkFindCubicMaxCurvature(const SkPoint src[4], SkScalar tValues[3]); int SkChopCubicAtMaxCurvature(const SkPoint src[4], SkPoint dst[13], SkScalar tValues[3] = nullptr); /** Returns t value of cusp if cubic has one; returns -1 otherwise. */ SkScalar SkFindCubicCusp(const SkPoint src[4]); /** Given a monotonically increasing or decreasing cubic bezier src, chop it * where the X value is the specified value. The returned cubics will be in * dst, sharing the middle point. That is, the first cubic is dst[0..3] and * the second dst[3..6]. * * If the cubic provided is *not* monotone, it will be chopped at the first * time the curve has the specified X value. * * If the cubic never reaches the specified value, the function returns false. */ bool SkChopMonoCubicAtX(const SkPoint src[4], SkScalar x, SkPoint dst[7]); /** Given a monotonically increasing or decreasing cubic bezier src, chop it * where the Y value is the specified value. The returned cubics will be in * dst, sharing the middle point. That is, the first cubic is dst[0..3] and * the second dst[3..6]. * * If the cubic provided is *not* monotone, it will be chopped at the first * time the curve has the specified Y value. * * If the cubic never reaches the specified value, the function returns false. */ bool SkChopMonoCubicAtY(const SkPoint src[4], SkScalar y, SkPoint dst[7]); enum class SkCubicType { kSerpentine, kLoop, kLocalCusp, // Cusp at a non-infinite parameter value with an inflection at t=infinity. kCuspAtInfinity, // Cusp with a cusp at t=infinity and a local inflection. kQuadratic, kLineOrPoint }; static inline bool SkCubicIsDegenerate(SkCubicType type) { switch (type) { case SkCubicType::kSerpentine: case SkCubicType::kLoop: case SkCubicType::kLocalCusp: case SkCubicType::kCuspAtInfinity: return false; case SkCubicType::kQuadratic: case SkCubicType::kLineOrPoint: return true; } SK_ABORT("Invalid SkCubicType"); } static inline const char* SkCubicTypeName(SkCubicType type) { switch (type) { case SkCubicType::kSerpentine: return "kSerpentine"; case SkCubicType::kLoop: return "kLoop"; case SkCubicType::kLocalCusp: return "kLocalCusp"; case SkCubicType::kCuspAtInfinity: return "kCuspAtInfinity"; case SkCubicType::kQuadratic: return "kQuadratic"; case SkCubicType::kLineOrPoint: return "kLineOrPoint"; } SK_ABORT("Invalid SkCubicType"); } /** Returns the cubic classification. t[],s[] are set to the two homogeneous parameter values at which points the lines L & M intersect with K, sorted from smallest to largest and oriented so positive values of the implicit are on the "left" side. For a serpentine curve they are the inflection points. For a loop they are the double point. For a local cusp, they are both equal and denote the cusp point. For a cusp at an infinite parameter value, one will be the local inflection point and the other +inf (t,s = 1,0). If the curve is degenerate (i.e. quadratic or linear) they are both set to a parameter value of +inf (t,s = 1,0). d[] is filled with the cubic inflection function coefficients. See "Resolution Independent Curve Rendering using Programmable Graphics Hardware", 4.2 Curve Categorization: If the input points contain infinities or NaN, the return values are undefined. https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf */ SkCubicType SkClassifyCubic(const SkPoint p[4], double t[2] = nullptr, double s[2] = nullptr, double d[4] = nullptr); /////////////////////////////////////////////////////////////////////////////// enum SkRotationDirection { kCW_SkRotationDirection, kCCW_SkRotationDirection }; struct SkConic { SkConic() {} SkConic(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, SkScalar w) { this->set(p0, p1, p2, w); } SkConic(const SkPoint pts[3], SkScalar w) { this->set(pts, w); } SkPoint fPts[3]; SkScalar fW; void set(const SkPoint pts[3], SkScalar w) { memcpy(fPts, pts, 3 * sizeof(SkPoint)); this->setW(w); } void set(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, SkScalar w) { fPts[0] = p0; fPts[1] = p1; fPts[2] = p2; this->setW(w); } void setW(SkScalar w) { if (SkScalarIsFinite(w)) { SkASSERT(w > 0); } // Guard against bad weights by forcing them to 1. fW = w > 0 && SkScalarIsFinite(w) ? w : 1; } /** * Given a t-value [0...1] return its position and/or tangent. * If pos is not null, return its position at the t-value. * If tangent is not null, return its tangent at the t-value. NOTE the * tangent value's length is arbitrary, and only its direction should * be used. */ void evalAt(SkScalar t, SkPoint* pos, SkVector* tangent = nullptr) const; [[nodiscard]] bool chopAt(SkScalar t, SkConic dst[2]) const; void chopAt(SkScalar t1, SkScalar t2, SkConic* dst) const; void chop(SkConic dst[2]) const; SkPoint evalAt(SkScalar t) const; SkVector evalTangentAt(SkScalar t) const; void computeAsQuadError(SkVector* err) const; bool asQuadTol(SkScalar tol) const; /** * return the power-of-2 number of quads needed to approximate this conic * with a sequence of quads. Will be >= 0. */ int SK_SPI computeQuadPOW2(SkScalar tol) const; /** * Chop this conic into N quads, stored continguously in pts[], where * N = 1 << pow2. The amount of storage needed is (1 + 2 * N) */ [[nodiscard]] int SK_SPI chopIntoQuadsPOW2(SkPoint pts[], int pow2) const; float findMidTangent() const; bool findXExtrema(SkScalar* t) const; bool findYExtrema(SkScalar* t) const; bool chopAtXExtrema(SkConic dst[2]) const; bool chopAtYExtrema(SkConic dst[2]) const; void computeTightBounds(SkRect* bounds) const; void computeFastBounds(SkRect* bounds) const; /** Find the parameter value where the conic takes on its maximum curvature. * * @param t output scalar for max curvature. Will be unchanged if * max curvature outside 0..1 range. * * @return true if max curvature found inside 0..1 range, false otherwise */ // bool findMaxCurvature(SkScalar* t) const; // unimplemented static SkScalar TransformW(const SkPoint[3], SkScalar w, const SkMatrix&); enum { kMaxConicsForArc = 5 }; static int BuildUnitArc(const SkVector& start, const SkVector& stop, SkRotationDirection, const SkMatrix*, SkConic conics[kMaxConicsForArc]); }; // inline helpers are contained in a namespace to avoid external leakage to fragile SkVx members namespace { // NOLINT(google-build-namespaces) /** * use for : eval(t) == A * t^2 + B * t + C */ struct SkQuadCoeff { SkQuadCoeff() {} SkQuadCoeff(const skvx::float2& A, const skvx::float2& B, const skvx::float2& C) : fA(A) , fB(B) , fC(C) { } SkQuadCoeff(const SkPoint src[3]) { fC = from_point(src[0]); auto P1 = from_point(src[1]); auto P2 = from_point(src[2]); fB = times_2(P1 - fC); fA = P2 - times_2(P1) + fC; } skvx::float2 eval(const skvx::float2& tt) { return (fA * tt + fB) * tt + fC; } skvx::float2 fA; skvx::float2 fB; skvx::float2 fC; }; struct SkConicCoeff { SkConicCoeff(const SkConic& conic) { skvx::float2 p0 = from_point(conic.fPts[0]); skvx::float2 p1 = from_point(conic.fPts[1]); skvx::float2 p2 = from_point(conic.fPts[2]); skvx::float2 ww(conic.fW); auto p1w = p1 * ww; fNumer.fC = p0; fNumer.fA = p2 - times_2(p1w) + p0; fNumer.fB = times_2(p1w - p0); fDenom.fC = 1; fDenom.fB = times_2(ww - fDenom.fC); fDenom.fA = 0 - fDenom.fB; } skvx::float2 eval(SkScalar t) { skvx::float2 tt(t); skvx::float2 numer = fNumer.eval(tt); skvx::float2 denom = fDenom.eval(tt); return numer / denom; } SkQuadCoeff fNumer; SkQuadCoeff fDenom; }; struct SkCubicCoeff { SkCubicCoeff(const SkPoint src[4]) { skvx::float2 P0 = from_point(src[0]); skvx::float2 P1 = from_point(src[1]); skvx::float2 P2 = from_point(src[2]); skvx::float2 P3 = from_point(src[3]); skvx::float2 three(3); fA = P3 + three * (P1 - P2) - P0; fB = three * (P2 - times_2(P1) + P0); fC = three * (P1 - P0); fD = P0; } skvx::float2 eval(const skvx::float2& t) { return ((fA * t + fB) * t + fC) * t + fD; } skvx::float2 fA; skvx::float2 fB; skvx::float2 fC; skvx::float2 fD; }; } // namespace /** * Help class to allocate storage for approximating a conic with N quads. */ class SkAutoConicToQuads { public: SkAutoConicToQuads() : fQuadCount(0) {} /** * Given a conic and a tolerance, return the array of points for the * approximating quad(s). Call countQuads() to know the number of quads * represented in these points. * * The quads are allocated to share end-points. e.g. if there are 4 quads, * there will be 9 points allocated as follows * quad[0] == pts[0..2] * quad[1] == pts[2..4] * quad[2] == pts[4..6] * quad[3] == pts[6..8] */ const SkPoint* computeQuads(const SkConic& conic, SkScalar tol) { int pow2 = conic.computeQuadPOW2(tol); fQuadCount = 1 << pow2; SkPoint* pts = fStorage.reset(1 + 2 * fQuadCount); fQuadCount = conic.chopIntoQuadsPOW2(pts, pow2); return pts; } const SkPoint* computeQuads(const SkPoint pts[3], SkScalar weight, SkScalar tol) { SkConic conic; conic.set(pts, weight); return computeQuads(conic, tol); } int countQuads() const { return fQuadCount; } private: enum { kQuadCount = 8, // should handle most conics kPointCount = 1 + 2 * kQuadCount, }; skia_private::AutoSTMalloc fStorage; int fQuadCount; // #quads for current usage }; class SkData; /** * Copy the provided stream to an SkData variable. * * Note: Assumes the stream is at the beginning. If it has a length, * but is not at the beginning, this call will fail (return NULL). * * @param stream SkStream to be copied into data. * @return The resulting SkData after the copy, nullptr on failure. */ sk_sp SkCopyStreamToData(SkStream* stream); /** * Copies the input stream from the current position to the end. * Does not rewind the input stream. */ bool SkStreamCopy(SkWStream* out, SkStream* input); /** A SkWStream that writes all output to SkDebugf, for debugging purposes. */ class SkDebugfStream final : public SkWStream { public: bool write(const void* buffer, size_t size) override; size_t bytesWritten() const override; private: size_t fBytesWritten = 0; }; // If the stream supports identifying the current position and total length, this returns // true if there are not enough bytes in the stream to fulfill a read of the given length. // Otherwise, it returns false. // False does *not* mean a read will succeed of the given length, but true means we are // certain it will fail. bool StreamRemainingLengthIsBelow(SkStream* stream, size_t len); struct SkRect; /** This is basically an iterator. It is initialized with an edge and a clip, and then next() is called until it returns kDone_Verb. */ class SkEdgeClipper { public: SkEdgeClipper(bool canCullToTheRight) : fCanCullToTheRight(canCullToTheRight) {} bool clipLine(SkPoint p0, SkPoint p1, const SkRect& clip); bool clipQuad(const SkPoint pts[3], const SkRect& clip); bool clipCubic(const SkPoint pts[4], const SkRect& clip); SkPath::Verb next(SkPoint pts[]); bool canCullToTheRight() const { return fCanCullToTheRight; } /** * Clips each segment from the path, and passes the result (in a clipper) to the * consume proc. */ static void ClipPath(const SkPath& path, const SkRect& clip, bool canCullToTheRight, void (*consume)(SkEdgeClipper*, bool newCtr, void* ctx), void* ctx); private: SkPoint* fCurrPoint; SkPath::Verb* fCurrVerb; const bool fCanCullToTheRight; enum { kMaxVerbs = 18, // max curvature in X and Y split cubic into 9 pieces, * (line + cubic) kMaxPoints = 54 // 2 lines + 1 cubic require 6 points; times 9 pieces }; SkPoint fPoints[kMaxPoints]; SkPath::Verb fVerbs[kMaxVerbs]; void clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip); void clipMonoCubic(const SkPoint srcPts[4], const SkRect& clip); void appendLine(SkPoint p0, SkPoint p1); void appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse); void appendQuad(const SkPoint pts[3], bool reverse); void appendCubic(const SkPoint pts[4], bool reverse); }; #ifdef SK_DEBUG void sk_assert_monotonic_x(const SkPoint pts[], int count); void sk_assert_monotonic_y(const SkPoint pts[], int count); #else #define sk_assert_monotonic_x(pts, count) #define sk_assert_monotonic_y(pts, count) #endif struct SkPoint; struct SkRect; class SkLineClipper { public: enum { kMaxPoints = 4, kMaxClippedLineSegments = kMaxPoints - 1 }; /* Clip the line pts[0]...pts[1] against clip, ignoring segments that lie completely above or below the clip. For portions to the left or right, turn those into vertical line segments that are aligned to the edge of the clip. Return the number of line segments that result, and store the end-points of those segments sequentially in lines as follows: 1st segment: lines[0]..lines[1] 2nd segment: lines[1]..lines[2] 3rd segment: lines[2]..lines[3] */ static int ClipLine(const SkPoint pts[2], const SkRect& clip, SkPoint lines[kMaxPoints], bool canCullToTheRight); /* Intersect the line segment against the rect. If there is a non-empty resulting segment, return true and set dst[] to that segment. If not, return false and ignore dst[]. ClipLine is specialized for scan-conversion, as it adds vertical segments on the sides to show where the line extended beyond the left or right sides. IntersectLine does not. */ static bool IntersectLine(const SkPoint src[2], const SkRect& clip, SkPoint dst[2]); }; enum class SkPathConvexity { kConvex, kConcave, kUnknown, }; enum class SkPathFirstDirection { kCW, // == SkPathDirection::kCW kCCW, // == SkPathDirection::kCCW kUnknown, }; class SkMatrix; class SkRRect; static_assert(0 == static_cast(SkPathFillType::kWinding), "fill_type_mismatch"); static_assert(1 == static_cast(SkPathFillType::kEvenOdd), "fill_type_mismatch"); static_assert(2 == static_cast(SkPathFillType::kInverseWinding), "fill_type_mismatch"); static_assert(3 == static_cast(SkPathFillType::kInverseEvenOdd), "fill_type_mismatch"); class SkPathPriv { public: // skbug.com/9906: Not a perfect solution for W plane clipping, but 1/16384 is a // reasonable limit (roughly 5e-5) inline static constexpr SkScalar kW0PlaneDistance = 1.f / (1 << 14); static SkPathFirstDirection AsFirstDirection(SkPathDirection dir) { // since we agree numerically for the values in Direction, we can just cast. return (SkPathFirstDirection)dir; } /** * Return the opposite of the specified direction. kUnknown is its own * opposite. */ static SkPathFirstDirection OppositeFirstDirection(SkPathFirstDirection dir) { static const SkPathFirstDirection gOppositeDir[] = { SkPathFirstDirection::kCCW, SkPathFirstDirection::kCW, SkPathFirstDirection::kUnknown, }; return gOppositeDir[(unsigned)dir]; } /** * Tries to compute the direction of the outer-most non-degenerate * contour. If it can be computed, return that direction. If it cannot be determined, * or the contour is known to be convex, return kUnknown. If the direction was determined, * it is cached to make subsequent calls return quickly. */ static SkPathFirstDirection ComputeFirstDirection(const SkPath&); static bool IsClosedSingleContour(const SkPath& path) { int verbCount = path.countVerbs(); if (verbCount == 0) return false; int moveCount = 0; auto verbs = path.fPathRef->verbsBegin(); for (int i = 0; i < verbCount; i++) { switch (verbs[i]) { case SkPath::Verb::kMove_Verb: moveCount += 1; if (moveCount > 1) { return false; } break; case SkPath::Verb::kClose_Verb: if (i == verbCount - 1) { return true; } return false; default: break; } } return false; } // In some scenarios (e.g. fill or convexity checking all but the last leading move to are // irrelevant to behavior). SkPath::injectMoveToIfNeeded should ensure that this is always at // least 1. static int LeadingMoveToCount(const SkPath& path) { int verbCount = path.countVerbs(); auto verbs = path.fPathRef->verbsBegin(); for (int i = 0; i < verbCount; i++) { if (verbs[i] != SkPath::Verb::kMove_Verb) { return i; } } return verbCount; // path is all move verbs } static void AddGenIDChangeListener(const SkPath& path, sk_sp listener) { path.fPathRef->addGenIDChangeListener(std::move(listener)); } /** * This returns true for a rect that has a move followed by 3 or 4 lines and a close. If * 'isSimpleFill' is true, an uncloseed rect will also be accepted as long as it starts and * ends at the same corner. This does not permit degenerate line or point rectangles. */ static bool IsSimpleRect(const SkPath& path, bool isSimpleFill, SkRect* rect, SkPathDirection* direction, unsigned* start); /** * Creates a path from arc params using the semantics of SkCanvas::drawArc. This function * assumes empty ovals and zero sweeps have already been filtered out. */ static void CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect); /** * Determines whether an arc produced by CreateDrawArcPath will be convex. Assumes a non-empty * oval. */ static bool DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect); static void ShrinkToFit(SkPath* path) { path->shrinkToFit(); } /** * Returns a C++11-iterable object that traverses a path's verbs in order. e.g: * * for (SkPath::Verb verb : SkPathPriv::Verbs(path)) { * ... * } */ struct Verbs { public: Verbs(const SkPath& path) : fPathRef(path.fPathRef.get()) {} struct Iter { void operator++() { fVerb++; } bool operator!=(const Iter& b) { return fVerb != b.fVerb; } SkPath::Verb operator*() { return static_cast(*fVerb); } const uint8_t* fVerb; }; Iter begin() { return Iter{fPathRef->verbsBegin()}; } Iter end() { return Iter{fPathRef->verbsEnd()}; } private: Verbs(const Verbs&) = delete; Verbs& operator=(const Verbs&) = delete; SkPathRef* fPathRef; }; /** * Iterates through a raw range of path verbs, points, and conics. All values are returned * unaltered. * * NOTE: This class's definition will be moved into SkPathPriv once RangeIter is removed. */ using RangeIter = SkPath::RangeIter; /** * Iterable object for traversing verbs, points, and conic weights in a path: * * for (auto [verb, pts, weights] : SkPathPriv::Iterate(skPath)) { * ... * } */ struct Iterate { public: Iterate(const SkPath& path) : Iterate(path.fPathRef->verbsBegin(), // Don't allow iteration through non-finite points. (!path.isFinite()) ? path.fPathRef->verbsBegin() : path.fPathRef->verbsEnd(), path.fPathRef->points(), path.fPathRef->conicWeights()) { } Iterate(const uint8_t* verbsBegin, const uint8_t* verbsEnd, const SkPoint* points, const SkScalar* weights) : fVerbsBegin(verbsBegin), fVerbsEnd(verbsEnd), fPoints(points), fWeights(weights) { } SkPath::RangeIter begin() { return {fVerbsBegin, fPoints, fWeights}; } SkPath::RangeIter end() { return {fVerbsEnd, nullptr, nullptr}; } private: const uint8_t* fVerbsBegin; const uint8_t* fVerbsEnd; const SkPoint* fPoints; const SkScalar* fWeights; }; /** * Returns a pointer to the verb data. */ static const uint8_t* VerbData(const SkPath& path) { return path.fPathRef->verbsBegin(); } /** Returns a raw pointer to the path points */ static const SkPoint* PointData(const SkPath& path) { return path.fPathRef->points(); } /** Returns the number of conic weights in the path */ static int ConicWeightCnt(const SkPath& path) { return path.fPathRef->countWeights(); } /** Returns a raw pointer to the path conic weights. */ static const SkScalar* ConicWeightData(const SkPath& path) { return path.fPathRef->conicWeights(); } /** Returns true if the underlying SkPathRef has one single owner. */ static bool TestingOnly_unique(const SkPath& path) { return path.fPathRef->unique(); } // Won't be needed once we can make path's immutable (with their bounds always computed) static bool HasComputedBounds(const SkPath& path) { return path.hasComputedBounds(); } /** Returns true if constructed by addCircle(), addOval(); and in some cases, addRoundRect(), addRRect(). SkPath constructed with conicTo() or rConicTo() will not return true though SkPath draws oval. rect receives bounds of oval. dir receives SkPathDirection of oval: kCW_Direction if clockwise, kCCW_Direction if counterclockwise. start receives start of oval: 0 for top, 1 for right, 2 for bottom, 3 for left. rect, dir, and start are unmodified if oval is not found. Triggers performance optimizations on some GPU surface implementations. @param rect storage for bounding SkRect of oval; may be nullptr @param dir storage for SkPathDirection; may be nullptr @param start storage for start of oval; may be nullptr @return true if SkPath was constructed by method that reduces to oval */ static bool IsOval(const SkPath& path, SkRect* rect, SkPathDirection* dir, unsigned* start) { bool isCCW = false; bool result = path.fPathRef->isOval(rect, &isCCW, start); if (dir && result) { *dir = isCCW ? SkPathDirection::kCCW : SkPathDirection::kCW; } return result; } /** Returns true if constructed by addRoundRect(), addRRect(); and if construction is not empty, not SkRect, and not oval. SkPath constructed with other calls will not return true though SkPath draws SkRRect. rrect receives bounds of SkRRect. dir receives SkPathDirection of oval: kCW_Direction if clockwise, kCCW_Direction if counterclockwise. start receives start of SkRRect: 0 for top, 1 for right, 2 for bottom, 3 for left. rrect, dir, and start are unmodified if SkRRect is not found. Triggers performance optimizations on some GPU surface implementations. @param rrect storage for bounding SkRect of SkRRect; may be nullptr @param dir storage for SkPathDirection; may be nullptr @param start storage for start of SkRRect; may be nullptr @return true if SkPath contains only SkRRect */ static bool IsRRect(const SkPath& path, SkRRect* rrect, SkPathDirection* dir, unsigned* start) { bool isCCW = false; bool result = path.fPathRef->isRRect(rrect, &isCCW, start); if (dir && result) { *dir = isCCW ? SkPathDirection::kCCW : SkPathDirection::kCW; } return result; } /** * Sometimes in the drawing pipeline, we have to perform math on path coordinates, even after * the path is in device-coordinates. Tessellation and clipping are two examples. Usually this * is pretty modest, but it can involve subtracting/adding coordinates, or multiplying by * small constants (e.g. 2,3,4). To try to preflight issues where these optionations could turn * finite path values into infinities (or NaNs), we allow the upper drawing code to reject * the path if its bounds (in device coordinates) is too close to max float. */ static bool TooBigForMath(const SkRect& bounds) { // This value is just a guess. smaller is safer, but we don't want to reject largish paths // that we don't have to. constexpr SkScalar scale_down_to_allow_for_small_multiplies = 0.25f; constexpr SkScalar max = SK_ScalarMax * scale_down_to_allow_for_small_multiplies; // use ! expression so we return true if bounds contains NaN return !(bounds.fLeft >= -max && bounds.fTop >= -max && bounds.fRight <= max && bounds.fBottom <= max); } static bool TooBigForMath(const SkPath& path) { return TooBigForMath(path.getBounds()); } // Returns number of valid points for each SkPath::Iter verb static int PtsInIter(unsigned verb) { static const uint8_t gPtsInVerb[] = { 1, // kMove pts[0] 2, // kLine pts[0..1] 3, // kQuad pts[0..2] 3, // kConic pts[0..2] 4, // kCubic pts[0..3] 0, // kClose 0 // kDone }; SkASSERT(verb < std::size(gPtsInVerb)); return gPtsInVerb[verb]; } // Returns number of valid points for each verb, not including the "starter" // point that the Iterator adds for line/quad/conic/cubic static int PtsInVerb(unsigned verb) { static const uint8_t gPtsInVerb[] = { 1, // kMove pts[0] 1, // kLine pts[0..1] 2, // kQuad pts[0..2] 2, // kConic pts[0..2] 3, // kCubic pts[0..3] 0, // kClose 0 // kDone }; SkASSERT(verb < std::size(gPtsInVerb)); return gPtsInVerb[verb]; } static bool IsAxisAligned(const SkPath& path); static bool AllPointsEq(const SkPoint pts[], int count) { for (int i = 1; i < count; ++i) { if (pts[0] != pts[i]) { return false; } } return true; } static int LastMoveToIndex(const SkPath& path) { return path.fLastMoveToIndex; } static bool IsRectContour(const SkPath&, bool allowPartial, int* currVerb, const SkPoint** ptsPtr, bool* isClosed, SkPathDirection* direction, SkRect* rect); /** Returns true if SkPath is equivalent to nested SkRect pair when filled. If false, rect and dirs are unchanged. If true, rect and dirs are written to if not nullptr: setting rect[0] to outer SkRect, and rect[1] to inner SkRect; setting dirs[0] to SkPathDirection of outer SkRect, and dirs[1] to SkPathDirection of inner SkRect. @param rect storage for SkRect pair; may be nullptr @param dirs storage for SkPathDirection pair; may be nullptr @return true if SkPath contains nested SkRect pair */ static bool IsNestedFillRects(const SkPath&, SkRect rect[2], SkPathDirection dirs[2] = nullptr); static bool IsInverseFillType(SkPathFillType fill) { return (static_cast(fill) & 2) != 0; } /** Returns equivalent SkPath::FillType representing SkPath fill inside its bounds. . @param fill one of: kWinding_FillType, kEvenOdd_FillType, kInverseWinding_FillType, kInverseEvenOdd_FillType @return fill, or kWinding_FillType or kEvenOdd_FillType if fill is inverted */ static SkPathFillType ConvertToNonInverseFillType(SkPathFillType fill) { return (SkPathFillType)(static_cast(fill) & 1); } /** * If needed (to not blow-up under a perspective matrix), clip the path, returning the * answer in "result", and return true. * * Note result might be empty (if the path was completely clipped out). * * If no clipping is needed, returns false and "result" is left unchanged. */ static bool PerspectiveClip(const SkPath& src, const SkMatrix&, SkPath* result); /** * Gets the number of GenIDChangeListeners. If another thread has access to this path then * this may be stale before return and only indicates that the count was the return value * at some point during the execution of the function. */ static int GenIDChangeListenersCount(const SkPath&); static void UpdatePathPoint(SkPath* path, int index, const SkPoint& pt) { SkASSERT(index < path->countPoints()); SkPathRef::Editor ed(&path->fPathRef); ed.writablePoints()[index] = pt; path->dirtyAfterEdit(); } static SkPathConvexity GetConvexity(const SkPath& path) { return path.getConvexity(); } static SkPathConvexity GetConvexityOrUnknown(const SkPath& path) { return path.getConvexityOrUnknown(); } static void SetConvexity(const SkPath& path, SkPathConvexity c) { path.setConvexity(c); } static void ForceComputeConvexity(const SkPath& path) { path.setConvexity(SkPathConvexity::kUnknown); (void)path.isConvex(); } static void ReverseAddPath(SkPathBuilder* builder, const SkPath& reverseMe) { builder->privateReverseAddPath(reverseMe); } static SkPath MakePath(const SkPathVerbAnalysis& analysis, const SkPoint points[], const uint8_t verbs[], int verbCount, const SkScalar conics[], SkPathFillType fillType, bool isVolatile) { return SkPath::MakeInternal(analysis, points, verbs, verbCount, conics, fillType, isVolatile); } }; // Lightweight variant of SkPath::Iter that only returns segments (e.g. lines/conics). // Does not return kMove or kClose. // Always "auto-closes" each contour. // Roughly the same as SkPath::Iter(path, true), but does not return moves or closes // class SkPathEdgeIter { const uint8_t* fVerbs; const uint8_t* fVerbsStop; const SkPoint* fPts; const SkPoint* fMoveToPtr; const SkScalar* fConicWeights; SkPoint fScratch[2]; // for auto-close lines bool fNeedsCloseLine; bool fNextIsNewContour; SkDEBUGCODE(bool fIsConic;) enum { kIllegalEdgeValue = 99 }; public: SkPathEdgeIter(const SkPath& path); SkScalar conicWeight() const { SkASSERT(fIsConic); return *fConicWeights; } enum class Edge { kLine = SkPath::kLine_Verb, kQuad = SkPath::kQuad_Verb, kConic = SkPath::kConic_Verb, kCubic = SkPath::kCubic_Verb, }; static SkPath::Verb EdgeToVerb(Edge e) { return SkPath::Verb(e); } struct Result { const SkPoint* fPts; // points for the segment, or null if done Edge fEdge; bool fIsNewContour; // Returns true when it holds an Edge, false when the path is done. explicit operator bool() { return fPts != nullptr; } }; Result next() { auto closeline = [&]() { fScratch[0] = fPts[-1]; fScratch[1] = *fMoveToPtr; fNeedsCloseLine = false; fNextIsNewContour = true; return Result{ fScratch, Edge::kLine, false }; }; for (;;) { SkASSERT(fVerbs <= fVerbsStop); if (fVerbs == fVerbsStop) { return fNeedsCloseLine ? closeline() : Result{ nullptr, Edge(kIllegalEdgeValue), false }; } SkDEBUGCODE(fIsConic = false;) const auto v = *fVerbs++; switch (v) { case SkPath::kMove_Verb: { if (fNeedsCloseLine) { auto res = closeline(); fMoveToPtr = fPts++; return res; } fMoveToPtr = fPts++; fNextIsNewContour = true; } break; case SkPath::kClose_Verb: if (fNeedsCloseLine) return closeline(); break; default: { // Actual edge. const int pts_count = (v+2) / 2, cws_count = (v & (v-1)) / 2; SkASSERT(pts_count == SkPathPriv::PtsInIter(v) - 1); fNeedsCloseLine = true; fPts += pts_count; fConicWeights += cws_count; SkDEBUGCODE(fIsConic = (v == SkPath::kConic_Verb);) SkASSERT(fIsConic == (cws_count > 0)); bool isNewContour = fNextIsNewContour; fNextIsNewContour = false; return { &fPts[-(pts_count + 1)], Edge(v), isNewContour }; } } } } }; class SkPointPriv { public: enum Side { kLeft_Side = -1, kOn_Side = 0, kRight_Side = 1, }; static bool AreFinite(const SkPoint array[], int count) { return SkScalarsAreFinite(&array[0].fX, count << 1); } static const SkScalar* AsScalars(const SkPoint& pt) { return &pt.fX; } static bool CanNormalize(SkScalar dx, SkScalar dy) { return SkScalarsAreFinite(dx, dy) && (dx || dy); } static SkScalar DistanceToLineBetweenSqd(const SkPoint& pt, const SkPoint& a, const SkPoint& b, Side* side = nullptr); static SkScalar DistanceToLineBetween(const SkPoint& pt, const SkPoint& a, const SkPoint& b, Side* side = nullptr) { return SkScalarSqrt(DistanceToLineBetweenSqd(pt, a, b, side)); } static SkScalar DistanceToLineSegmentBetweenSqd(const SkPoint& pt, const SkPoint& a, const SkPoint& b); static SkScalar DistanceToLineSegmentBetween(const SkPoint& pt, const SkPoint& a, const SkPoint& b) { return SkScalarSqrt(DistanceToLineSegmentBetweenSqd(pt, a, b)); } static SkScalar DistanceToSqd(const SkPoint& pt, const SkPoint& a) { SkScalar dx = pt.fX - a.fX; SkScalar dy = pt.fY - a.fY; return dx * dx + dy * dy; } static bool EqualsWithinTolerance(const SkPoint& p1, const SkPoint& p2) { return !CanNormalize(p1.fX - p2.fX, p1.fY - p2.fY); } static bool EqualsWithinTolerance(const SkPoint& pt, const SkPoint& p, SkScalar tol) { return SkScalarNearlyZero(pt.fX - p.fX, tol) && SkScalarNearlyZero(pt.fY - p.fY, tol); } static SkScalar LengthSqd(const SkPoint& pt) { return SkPoint::DotProduct(pt, pt); } static void Negate(SkIPoint& pt) { pt.fX = -pt.fX; pt.fY = -pt.fY; } static void RotateCCW(const SkPoint& src, SkPoint* dst) { // use a tmp in case src == dst SkScalar tmp = src.fX; dst->fX = src.fY; dst->fY = -tmp; } static void RotateCCW(SkPoint* pt) { RotateCCW(*pt, pt); } static void RotateCW(const SkPoint& src, SkPoint* dst) { // use a tmp in case src == dst SkScalar tmp = src.fX; dst->fX = -src.fY; dst->fY = tmp; } static void RotateCW(SkPoint* pt) { RotateCW(*pt, pt); } static bool SetLengthFast(SkPoint* pt, float length); static SkPoint MakeOrthog(const SkPoint& vec, Side side = kLeft_Side) { SkASSERT(side == kRight_Side || side == kLeft_Side); return (side == kRight_Side) ? SkPoint{-vec.fY, vec.fX} : SkPoint{vec.fY, -vec.fX}; } // counter-clockwise fan static void SetRectFan(SkPoint v[], SkScalar l, SkScalar t, SkScalar r, SkScalar b, size_t stride) { SkASSERT(stride >= sizeof(SkPoint)); ((SkPoint*)((intptr_t)v + 0 * stride))->set(l, t); ((SkPoint*)((intptr_t)v + 1 * stride))->set(l, b); ((SkPoint*)((intptr_t)v + 2 * stride))->set(r, b); ((SkPoint*)((intptr_t)v + 3 * stride))->set(r, t); } // tri strip with two counter-clockwise triangles static void SetRectTriStrip(SkPoint v[], SkScalar l, SkScalar t, SkScalar r, SkScalar b, size_t stride) { SkASSERT(stride >= sizeof(SkPoint)); ((SkPoint*)((intptr_t)v + 0 * stride))->set(l, t); ((SkPoint*)((intptr_t)v + 1 * stride))->set(l, b); ((SkPoint*)((intptr_t)v + 2 * stride))->set(r, t); ((SkPoint*)((intptr_t)v + 3 * stride))->set(r, b); } static void SetRectTriStrip(SkPoint v[], const SkRect& rect, size_t stride) { SetRectTriStrip(v, rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, stride); } }; /** * Computes the inverse of `inMatrix`, passed in column-major order. * `inMatrix` and `outMatrix` are allowed to point to the same array of scalars in memory. * `outMatrix` is allowed to be null. * The return value is the determinant of the input matrix. If zero is returned, the matrix was * non-invertible, and `outMatrix` has been left in an indeterminate state. */ SkScalar SkInvert2x2Matrix(const SkScalar inMatrix[4], SkScalar outMatrix[4]); SkScalar SkInvert3x3Matrix(const SkScalar inMatrix[9], SkScalar outMatrix[9]); SkScalar SkInvert4x4Matrix(const SkScalar inMatrix[16], SkScalar outMatrix[16]); struct SkPoint3; class SkMatrixPriv { public: enum { // writeTo/readFromMemory will never return a value larger than this kMaxFlattenSize = 9 * sizeof(SkScalar) + sizeof(uint32_t), }; static size_t WriteToMemory(const SkMatrix& matrix, void* buffer) { return matrix.writeToMemory(buffer); } static size_t ReadFromMemory(SkMatrix* matrix, const void* buffer, size_t length) { return matrix->readFromMemory(buffer, length); } typedef SkMatrix::MapXYProc MapXYProc; typedef SkMatrix::MapPtsProc MapPtsProc; static MapPtsProc GetMapPtsProc(const SkMatrix& matrix) { return SkMatrix::GetMapPtsProc(matrix.getType()); } static MapXYProc GetMapXYProc(const SkMatrix& matrix) { return SkMatrix::GetMapXYProc(matrix.getType()); } /** * Attempt to map the rect through the inverse of the matrix. If it is not invertible, * then this returns false and dst is unchanged. */ [[nodiscard]] static bool InverseMapRect(const SkMatrix& mx, SkRect* dst, const SkRect& src) { if (mx.isScaleTranslate()) { // A scale-translate matrix with a 0 scale factor is not invertible. if (mx.getScaleX() == 0.f || mx.getScaleY() == 0.f) { return false; } const SkScalar tx = mx.getTranslateX(); const SkScalar ty = mx.getTranslateY(); // mx maps coordinates as ((sx*x + tx), (sy*y + ty)) so the inverse is // ((x - tx)/sx), (y - ty)/sy). If sx or sy are negative, we have to swap the edge // values to maintain a sorted rect. auto inverted = skvx::float4::Load(&src.fLeft); inverted -= skvx::float4(tx, ty, tx, ty); if (mx.getType() > SkMatrix::kTranslate_Mask) { const SkScalar sx = 1.f / mx.getScaleX(); const SkScalar sy = 1.f / mx.getScaleY(); inverted *= skvx::float4(sx, sy, sx, sy); if (sx < 0.f && sy < 0.f) { inverted = skvx::shuffle<2, 3, 0, 1>(inverted); // swap L|R and T|B } else if (sx < 0.f) { inverted = skvx::shuffle<2, 1, 0, 3>(inverted); // swap L|R } else if (sy < 0.f) { inverted = skvx::shuffle<0, 3, 2, 1>(inverted); // swap T|B } } inverted.store(&dst->fLeft); return true; } // general case SkMatrix inverse; if (mx.invert(&inverse)) { inverse.mapRect(dst, src); return true; } return false; } /** Maps count pts, skipping stride bytes to advance from one SkPoint to the next. Points are mapped by multiplying each SkPoint by SkMatrix. Given: | A B C | | x | Matrix = | D E F |, pt = | y | | G H I | | 1 | each resulting pts SkPoint is computed as: |A B C| |x| Ax+By+C Dx+Ey+F Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- |G H I| |1| Gx+Hy+I Gx+Hy+I @param mx matrix used to map the points @param pts storage for mapped points @param stride size of record starting with SkPoint, in bytes @param count number of points to transform */ static void MapPointsWithStride(const SkMatrix& mx, SkPoint pts[], size_t stride, int count) { SkASSERT(stride >= sizeof(SkPoint)); SkASSERT(0 == stride % sizeof(SkScalar)); SkMatrix::TypeMask tm = mx.getType(); if (SkMatrix::kIdentity_Mask == tm) { return; } if (SkMatrix::kTranslate_Mask == tm) { const SkScalar tx = mx.getTranslateX(); const SkScalar ty = mx.getTranslateY(); skvx::float2 trans(tx, ty); for (int i = 0; i < count; ++i) { (skvx::float2::Load(&pts->fX) + trans).store(&pts->fX); pts = (SkPoint*)((intptr_t)pts + stride); } return; } // Insert other special-cases here (e.g. scale+translate) // general case SkMatrix::MapXYProc proc = mx.getMapXYProc(); for (int i = 0; i < count; ++i) { proc(mx, pts->fX, pts->fY, pts); pts = (SkPoint*)((intptr_t)pts + stride); } } /** Maps src SkPoint array of length count to dst SkPoint array, skipping stride bytes to advance from one SkPoint to the next. Points are mapped by multiplying each SkPoint by SkMatrix. Given: | A B C | | x | Matrix = | D E F |, src = | y | | G H I | | 1 | each resulting dst SkPoint is computed as: |A B C| |x| Ax+By+C Dx+Ey+F Matrix * pt = |D E F| |y| = |Ax+By+C Dx+Ey+F Gx+Hy+I| = ------- , ------- |G H I| |1| Gx+Hy+I Gx+Hy+I @param mx matrix used to map the points @param dst storage for mapped points @param src points to transform @param stride size of record starting with SkPoint, in bytes @param count number of points to transform */ static void MapPointsWithStride(const SkMatrix& mx, SkPoint dst[], size_t dstStride, const SkPoint src[], size_t srcStride, int count) { SkASSERT(srcStride >= sizeof(SkPoint)); SkASSERT(dstStride >= sizeof(SkPoint)); SkASSERT(0 == srcStride % sizeof(SkScalar)); SkASSERT(0 == dstStride % sizeof(SkScalar)); for (int i = 0; i < count; ++i) { mx.mapPoints(dst, src, 1); src = (SkPoint*)((intptr_t)src + srcStride); dst = (SkPoint*)((intptr_t)dst + dstStride); } } static void MapHomogeneousPointsWithStride(const SkMatrix& mx, SkPoint3 dst[], size_t dstStride, const SkPoint3 src[], size_t srcStride, int count); static bool PostIDiv(SkMatrix* matrix, int divx, int divy) { return matrix->postIDiv(divx, divy); } static bool CheapEqual(const SkMatrix& a, const SkMatrix& b) { return &a == &b || 0 == memcmp(a.fMat, b.fMat, sizeof(a.fMat)); } static const SkScalar* M44ColMajor(const SkM44& m) { return m.fMat; } // This is legacy functionality that only checks the 3x3 portion. The matrix could have Z-based // shear, or other complex behavior. Only use this if you're planning to use the information // to accelerate some purely 2D operation. static bool IsScaleTranslateAsM33(const SkM44& m) { return m.rc(1,0) == 0 && m.rc(3,0) == 0 && m.rc(0,1) == 0 && m.rc(3,1) == 0 && m.rc(3,3) == 1; } // Map the four corners of 'r' and return the bounding box of those points. The four corners of // 'r' are assumed to have z = 0 and w = 1. If the matrix has perspective, the returned // rectangle will be the bounding box of the projected points after being clipped to w > 0. static SkRect MapRect(const SkM44& m, const SkRect& r); // Returns the differential area scale factor for a local point 'p' that will be transformed // by 'm' (which may have perspective). If 'm' does not have perspective, this scale factor is // constant regardless of 'p'; when it does have perspective, it is specific to that point. // // This can be crudely thought of as "device pixel area" / "local pixel area" at 'p'. // // Returns positive infinity if the transformed homogeneous point has w <= 0. static SkScalar DifferentialAreaScale(const SkMatrix& m, const SkPoint& p); // Determines if the transformation m applied to the bounds can be approximated by // an affine transformation, i.e., the perspective part of the transformation has little // visible effect. static bool NearlyAffine(const SkMatrix& m, const SkRect& bounds, SkScalar tolerance = SK_ScalarNearlyZero); static SkScalar ComputeResScaleForStroking(const SkMatrix& matrix); }; class SkMatrix; struct SkSamplingOptions; /** * Given a matrix, size and an antialias setting, return true if the computed dst-rect * would align such that there is a 1-to-1 coorspondence between src and dst pixels. * This can be called by drawing code to see if drawBitmap can be turned into * drawSprite (which is faster). * * The src-rect is defined to be { 0, 0, size.width(), size.height() } */ bool SkTreatAsSprite(const SkMatrix&, const SkISize& size, const SkSamplingOptions&, bool isAntiAlias); /** Decomposes the upper-left 2x2 of the matrix into a rotation (represented by the cosine and sine of the rotation angle), followed by a non-uniform scale, followed by another rotation. If there is a reflection, one of the scale factors will be negative. Returns true if successful. Returns false if the matrix is degenerate. */ bool SkDecomposeUpper2x2(const SkMatrix& matrix, SkPoint* rotation1, SkPoint* scale, SkPoint* rotation2); class SkReadBuffer; class SkWriteBuffer; // Given a src rect in texels to be filtered, this number of surrounding texels are needed by // the kernel in x and y. static constexpr int kBicubicFilterTexelPad = 2; // Private copy of SkFilterQuality, just for legacy deserialization // Matches values in SkFilterQuality enum SkLegacyFQ { kNone_SkLegacyFQ = 0, //!< nearest-neighbor; fastest but lowest quality kLow_SkLegacyFQ = 1, //!< bilerp kMedium_SkLegacyFQ = 2, //!< bilerp + mipmaps; good for down-scaling kHigh_SkLegacyFQ = 3, //!< bicubic resampling; slowest but good quality kLast_SkLegacyFQ = kHigh_SkLegacyFQ, }; // Matches values in SkSamplingOptions::MediumBehavior enum SkMediumAs { kNearest_SkMediumAs, kLinear_SkMediumAs, }; class SkSamplingPriv { public: static size_t FlatSize(const SkSamplingOptions& options) { size_t size = sizeof(uint32_t); // maxAniso if (!options.isAniso()) { size += 3 * sizeof(uint32_t); // bool32 + [2 floats | 2 ints] } return size; } // Returns true if the sampling can be ignored when the CTM is identity. static bool NoChangeWithIdentityMatrix(const SkSamplingOptions& sampling) { // If B == 0, the cubic resampler should have no effect for identity matrices // https://entropymine.com/imageworsener/bicubic/ // We assume aniso has no effect with an identity transform. return !sampling.useCubic || sampling.cubic.B == 0; } // Makes a fallback SkSamplingOptions for cases where anisotropic filtering is not allowed. // anisotropic filtering can access mip levels if present, but we don't add mipmaps to non- // mipmapped images when the user requests anisotropic. So we shouldn't fall back to a // sampling that would trigger mip map creation. static SkSamplingOptions AnisoFallback(bool imageIsMipped) { auto mm = imageIsMipped ? SkMipmapMode::kLinear : SkMipmapMode::kNone; return SkSamplingOptions(SkFilterMode::kLinear, mm); } static SkSamplingOptions FromFQ(SkLegacyFQ fq, SkMediumAs behavior = kNearest_SkMediumAs) { switch (fq) { case kHigh_SkLegacyFQ: return SkSamplingOptions(SkCubicResampler{1/3.0f, 1/3.0f}); case kMedium_SkLegacyFQ: return SkSamplingOptions(SkFilterMode::kLinear, behavior == kNearest_SkMediumAs ? SkMipmapMode::kNearest : SkMipmapMode::kLinear); case kLow_SkLegacyFQ: return SkSamplingOptions(SkFilterMode::kLinear, SkMipmapMode::kNone); case kNone_SkLegacyFQ: break; } return SkSamplingOptions(SkFilterMode::kNearest, SkMipmapMode::kNone); } }; template class SkPath_PointIterator { public: SkPath_PointIterator(SkPathDirection dir, unsigned startIndex) : fCurrent(startIndex % N) , fAdvance(dir == SkPathDirection::kCW ? 1 : N - 1) { } const SkPoint& current() const { SkASSERT(fCurrent < N); return fPts[fCurrent]; } const SkPoint& next() { fCurrent = (fCurrent + fAdvance) % N; return this->current(); } protected: SkPoint fPts[N]; private: unsigned fCurrent; unsigned fAdvance; }; class SkPath_RectPointIterator : public SkPath_PointIterator<4> { public: SkPath_RectPointIterator(const SkRect& rect, SkPathDirection dir, unsigned startIndex) : SkPath_PointIterator(dir, startIndex) { fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop); fPts[1] = SkPoint::Make(rect.fRight, rect.fTop); fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom); fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom); } }; class SkPath_OvalPointIterator : public SkPath_PointIterator<4> { public: SkPath_OvalPointIterator(const SkRect& oval, SkPathDirection dir, unsigned startIndex) : SkPath_PointIterator(dir, startIndex) { const SkScalar cx = oval.centerX(); const SkScalar cy = oval.centerY(); fPts[0] = SkPoint::Make(cx, oval.fTop); fPts[1] = SkPoint::Make(oval.fRight, cy); fPts[2] = SkPoint::Make(cx, oval.fBottom); fPts[3] = SkPoint::Make(oval.fLeft, cy); } }; class SkPath_RRectPointIterator : public SkPath_PointIterator<8> { public: SkPath_RRectPointIterator(const SkRRect& rrect, SkPathDirection dir, unsigned startIndex) : SkPath_PointIterator(dir, startIndex) { const SkRect& bounds = rrect.getBounds(); const SkScalar L = bounds.fLeft; const SkScalar T = bounds.fTop; const SkScalar R = bounds.fRight; const SkScalar B = bounds.fBottom; fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T); fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T); fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY); fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY); fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B); fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B); fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY); fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY); } }; enum SkScalarAsStringType { kDec_SkScalarAsStringType, kHex_SkScalarAsStringType, }; void SkAppendScalar(SkString*, SkScalar, SkScalarAsStringType); static inline void SkAppendScalarDec(SkString* str, SkScalar value) { SkAppendScalar(str, value, kDec_SkScalarAsStringType); } static inline void SkAppendScalarHex(SkString* str, SkScalar value) { SkAppendScalar(str, value, kHex_SkScalarAsStringType); } /** Indents every non-empty line of the string by tabCnt tabs */ SkString SkTabString(const SkString& string, int tabCnt); SkString SkStringFromUTF16(const uint16_t* src, size_t count); #if defined(SK_BUILD_FOR_WIN) #define SK_strcasecmp _stricmp #else #define SK_strcasecmp strcasecmp #endif enum SkStrSplitMode { // Strictly return all results. If the input is ",," and the separator is ',' this will return // an array of three empty strings. kStrict_SkStrSplitMode, // Only nonempty results will be added to the results. Multiple separators will be // coalesced. Separators at the beginning and end of the input will be ignored. If the input is // ",," and the separator is ',', this will return an empty vector. kCoalesce_SkStrSplitMode }; // Split str on any characters in delimiters into out. (strtok with a non-destructive API.) void SkStrSplit(const char* str, const char* delimiters, SkStrSplitMode splitMode, skia_private::TArray* out); inline void SkStrSplit( const char* str, const char* delimiters, skia_private::TArray* out) { SkStrSplit(str, delimiters, kCoalesce_SkStrSplitMode, out); } class SkM44; class SkMatrix; class SkRectPriv { public: // Returns an irect that is very large, and can be safely round-trip with SkRect and still // be considered non-empty (i.e. width/height > 0) even if we round-out the SkRect. static SkIRect MakeILarge() { // SK_MaxS32 >> 1 seemed better, but it did not survive round-trip with SkRect and rounding. // Also, 1 << 29 can be perfectly represented in float, while SK_MaxS32 >> 1 cannot. const int32_t large = 1 << 29; return { -large, -large, large, large }; } static SkIRect MakeILargestInverted() { return { SK_MaxS32, SK_MaxS32, SK_MinS32, SK_MinS32 }; } static SkRect MakeLargeS32() { SkRect r; r.set(MakeILarge()); return r; } static SkRect MakeLargest() { return { SK_ScalarMin, SK_ScalarMin, SK_ScalarMax, SK_ScalarMax }; } static constexpr SkRect MakeLargestInverted() { return { SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin }; } static void GrowToInclude(SkRect* r, const SkPoint& pt) { r->fLeft = std::min(pt.fX, r->fLeft); r->fRight = std::max(pt.fX, r->fRight); r->fTop = std::min(pt.fY, r->fTop); r->fBottom = std::max(pt.fY, r->fBottom); } // Conservative check if r can be expressed in fixed-point. // Will return false for very large values that might have fit static bool FitsInFixed(const SkRect& r) { return SkFitsInFixed(r.fLeft) && SkFitsInFixed(r.fTop) && SkFitsInFixed(r.fRight) && SkFitsInFixed(r.fBottom); } static bool Is16Bit(const SkIRect& r) { return SkTFitsIn(r.fLeft) && SkTFitsIn(r.fTop) && SkTFitsIn(r.fRight) && SkTFitsIn(r.fBottom); } // Returns r.width()/2 but divides first to avoid width() overflowing. static constexpr float HalfWidth(const SkRect& r) { return sk_float_midpoint(-r.fLeft, r.fRight); } // Returns r.height()/2 but divides first to avoid height() overflowing. static constexpr float HalfHeight(const SkRect& r) { return sk_float_midpoint(-r.fTop, r.fBottom); } // Evaluate A-B. If the difference shape cannot be represented as a rectangle then false is // returned and 'out' is set to the largest rectangle contained in said shape. If true is // returned then A-B is representable as a rectangle, which is stored in 'out'. static bool Subtract(const SkRect& a, const SkRect& b, SkRect* out); static bool Subtract(const SkIRect& a, const SkIRect& b, SkIRect* out); // Evaluate A-B, and return the largest rectangle contained in that shape (since the difference // may not be representable as rectangle). The returned rectangle will not intersect B. static SkRect Subtract(const SkRect& a, const SkRect& b) { SkRect diff; Subtract(a, b, &diff); return diff; } static SkIRect Subtract(const SkIRect& a, const SkIRect& b) { SkIRect diff; Subtract(a, b, &diff); return diff; } // Returns true if the quadrilateral formed by transforming the four corners of 'a' contains 'b' static bool QuadContainsRect(const SkMatrix& m, const SkIRect& a, const SkIRect& b); static bool QuadContainsRect(const SkM44& m, const SkRect& a, const SkRect& b); // Assuming 'src' does not intersect 'dst', returns the edge or corner of 'src' that is closest // to 'dst', e.g. the pixels that would be sampled from 'src' when clamp-tiled into 'dst'. // // The returned rectangle will not be empty if 'src' is not empty and 'dst' is not empty. // At least one of its width or height will be equal to 1 (possibly both if a corner is closest) // // Returns src.intersect(dst) if they do actually intersect. static SkIRect ClosestDisjointEdge(const SkIRect& src, const SkIRect& dst); }; class SkRBuffer; class SkWBuffer; class SkRRectPriv { public: static bool IsCircle(const SkRRect& rr) { return rr.isOval() && SkScalarNearlyEqual(rr.fRadii[0].fX, rr.fRadii[0].fY); } static SkVector GetSimpleRadii(const SkRRect& rr) { SkASSERT(!rr.isComplex()); return rr.fRadii[0]; } static bool IsSimpleCircular(const SkRRect& rr) { return rr.isSimple() && SkScalarNearlyEqual(rr.fRadii[0].fX, rr.fRadii[0].fY); } // Looser version of IsSimpleCircular, where the x & y values of the radii // only have to be nearly equal instead of strictly equal. static bool IsNearlySimpleCircular(const SkRRect& rr, SkScalar tolerance = SK_ScalarNearlyZero); static bool EqualRadii(const SkRRect& rr) { return rr.isRect() || SkRRectPriv::IsCircle(rr) || SkRRectPriv::IsSimpleCircular(rr); } static const SkVector* GetRadiiArray(const SkRRect& rr) { return rr.fRadii; } static bool AllCornersCircular(const SkRRect& rr, SkScalar tolerance = SK_ScalarNearlyZero); static bool ReadFromBuffer(SkRBuffer* buffer, SkRRect* rr); static void WriteToBuffer(const SkRRect& rr, SkWBuffer* buffer); // Test if a point is in the rrect, if it were a closed set. static bool ContainsPoint(const SkRRect& rr, const SkPoint& p) { return rr.getBounds().contains(p.fX, p.fY) && rr.checkCornerContainment(p.fX, p.fY); } // Compute an approximate largest inscribed bounding box of the rounded rect. For empty, // rect, oval, and simple types this will be the largest inscribed rectangle. Otherwise it may // not be the global maximum, but will be non-empty, touch at least one edge and be contained // in the round rect. static SkRect InnerBounds(const SkRRect& rr); // Attempt to compute the intersection of two round rects. The intersection is not necessarily // a round rect. This returns intersections only when the shape is representable as a new // round rect (or rect). Empty is returned if 'a' and 'b' do not intersect or if the // intersection is too complicated. This is conservative, it may not always detect that an // intersection could be represented as a round rect. However, when it does return a round rect // that intersection will be exact (i.e. it is NOT just a subset of the actual intersection). static SkRRect ConservativeIntersect(const SkRRect& a, const SkRRect& b); }; class SkScaleToSides { public: // This code assumes that a and b fit in a float, and therefore the resulting smaller value // of a and b will fit in a float. The side of the rectangle may be larger than a float. // Scale must be less than or equal to the ratio limit / (*a + *b). // This code assumes that NaN and Inf are never passed in. static void AdjustRadii(double limit, double scale, SkScalar* a, SkScalar* b) { SkASSERTF(scale < 1.0 && scale > 0.0, "scale: %g", scale); *a = (float)((double)*a * scale); *b = (float)((double)*b * scale); if (*a + *b > limit) { float* minRadius = a; float* maxRadius = b; // Force minRadius to be the smaller of the two. if (*minRadius > *maxRadius) { using std::swap; swap(minRadius, maxRadius); } // newMinRadius must be float in order to give the actual value of the radius. // The newMinRadius will always be smaller than limit. The largest that minRadius can be // is 1/2 the ratio of minRadius : (minRadius + maxRadius), therefore in the resulting // division, minRadius can be no larger than 1/2 limit + ULP. float newMinRadius = *minRadius; float newMaxRadius = (float)(limit - newMinRadius); // Reduce newMaxRadius an ulp at a time until it fits. This usually never happens, // but if it does it could be 1 or 2 times. In certain pathological cases it could be // more. Max iterations seen so far is 17. while (newMaxRadius + newMinRadius > limit) { newMaxRadius = nextafterf(newMaxRadius, 0.0f); } *maxRadius = newMaxRadius; } SkASSERTF(*a >= 0.0f && *b >= 0.0f, "a: %g, b: %g, limit: %g, scale: %g", *a, *b, limit, scale); SkASSERTF(*a + *b <= limit, "\nlimit: %.17f, sum: %.17f, a: %.10f, b: %.10f, scale: %.20f", limit, *a + *b, *a, *b, scale); } }; class SkOpCoincidence; class SkOpContour; bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence); class SkOpAngle; class SkOpCoincidence; class SkOpContour; class SkOpContourHead; class SkOpPtT; class SkOpSegment; class SkOpSpan; class SkOpSpanBase; class SkPath; struct SkDConic; struct SkDCubic; struct SkDLine; struct SkDPoint; struct SkDQuad; // define this when running fuzz // #define SK_BUILD_FOR_FUZZER #ifdef SK_RELEASE #define FORCE_RELEASE 1 #else #define FORCE_RELEASE 1 // set force release to 1 for multiple thread -- no debugging #endif #define DEBUG_UNDER_DEVELOPMENT 0 #define ONE_OFF_DEBUG 0 #define ONE_OFF_DEBUG_MATHEMATICA 0 #if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_ANDROID) #define SK_RAND(seed) rand() #else #define SK_RAND(seed) rand_r(&seed) #endif #define WIND_AS_STRING(x) char x##Str[12]; \ if (!SkPathOpsDebug::ValidWind(x)) strcpy(x##Str, "?"); \ else snprintf(x##Str, sizeof(x##Str), "%d", x) #if FORCE_RELEASE #define DEBUG_ACTIVE_OP 0 #define DEBUG_ACTIVE_SPANS 0 #define DEBUG_ADD_INTERSECTING_TS 0 #define DEBUG_ADD_T 0 #define DEBUG_ALIGNMENT 0 #define DEBUG_ANGLE 0 #define DEBUG_ASSEMBLE 0 #define DEBUG_COINCIDENCE 0 #define DEBUG_COINCIDENCE_DUMP 0 // accumulate and dump which algorithms fired #define DEBUG_COINCIDENCE_ORDER 0 // for well behaved curves, check if pairs match up in t-order #define DEBUG_COINCIDENCE_VERBOSE 0 // usually whether the next function generates coincidence #define DEBUG_CUBIC_BINARY_SEARCH 0 #define DEBUG_CUBIC_SPLIT 0 #define DEBUG_DUMP_SEGMENTS 0 #define DEBUG_DUMP_VERIFY 0 #define DEBUG_FLOW 0 #define DEBUG_LIMIT_WIND_SUM 0 #define DEBUG_MARK_DONE 0 #define DEBUG_PATH_CONSTRUCTION 0 #define DEBUG_PERP 0 #define DEBUG_SORT 0 #define DEBUG_T_SECT 0 #define DEBUG_T_SECT_DUMP 0 #define DEBUG_T_SECT_LOOP_COUNT 0 #define DEBUG_VALIDATE 0 #define DEBUG_WINDING 0 #define DEBUG_WINDING_AT_T 0 #else #define DEBUG_ACTIVE_OP 1 #define DEBUG_ACTIVE_SPANS 1 #define DEBUG_ADD_INTERSECTING_TS 1 #define DEBUG_ADD_T 1 #define DEBUG_ALIGNMENT 0 #define DEBUG_ANGLE 1 #define DEBUG_ASSEMBLE 1 #define DEBUG_COINCIDENCE 1 #define DEBUG_COINCIDENCE_DUMP 1 #define DEBUG_COINCIDENCE_ORDER 1 // tight arc quads may generate out-of-order coincidence spans #define DEBUG_COINCIDENCE_VERBOSE 1 #define DEBUG_CUBIC_BINARY_SEARCH 0 #define DEBUG_CUBIC_SPLIT 1 #define DEBUG_DUMP_VERIFY 1 #define DEBUG_DUMP_SEGMENTS 1 #define DEBUG_FLOW 1 #define DEBUG_LIMIT_WIND_SUM 15 #define DEBUG_MARK_DONE 1 #define DEBUG_PATH_CONSTRUCTION 1 #define DEBUG_PERP 1 #define DEBUG_SORT 1 #define DEBUG_T_SECT 0 // enabling may trigger validate asserts even though op does not fail #define DEBUG_T_SECT_DUMP 0 // Use 1 normally. Use 2 to number segments, 3 for script output #define DEBUG_T_SECT_LOOP_COUNT 0 #define DEBUG_VALIDATE 1 #define DEBUG_WINDING 1 #define DEBUG_WINDING_AT_T 1 #endif #ifdef SK_RELEASE #define SkDEBUGRELEASE(a, b) b #define SkDEBUGPARAMS(...) #else #define SkDEBUGRELEASE(a, b) a #define SkDEBUGPARAMS(...) , __VA_ARGS__ #endif #if DEBUG_VALIDATE == 0 #define PATH_OPS_DEBUG_VALIDATE_PARAMS(...) #else #define PATH_OPS_DEBUG_VALIDATE_PARAMS(...) , __VA_ARGS__ #endif #if DEBUG_T_SECT == 0 #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) b #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) #define PATH_OPS_DEBUG_T_SECT_CODE(...) #else #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) a #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) , __VA_ARGS__ #define PATH_OPS_DEBUG_T_SECT_CODE(...) __VA_ARGS__ #endif #if DEBUG_T_SECT_DUMP > 1 extern int gDumpTSectNum; #endif #if DEBUG_COINCIDENCE || DEBUG_COINCIDENCE_DUMP #define DEBUG_COIN 1 #else #define DEBUG_COIN 0 #endif #if DEBUG_COIN enum class SkOpPhase : char; #define DEBUG_COIN_DECLARE_ONLY_PARAMS() \ int lineNo, SkOpPhase phase, int iteration #define DEBUG_COIN_DECLARE_PARAMS() \ , DEBUG_COIN_DECLARE_ONLY_PARAMS() #define DEBUG_COIN_ONLY_PARAMS() \ __LINE__, SkOpPhase::kNoChange, 0 #define DEBUG_COIN_PARAMS() \ , DEBUG_COIN_ONLY_PARAMS() #define DEBUG_ITER_ONLY_PARAMS(iteration) \ __LINE__, SkOpPhase::kNoChange, iteration #define DEBUG_ITER_PARAMS(iteration) \ , DEBUG_ITER_ONLY_PARAMS(iteration) #define DEBUG_PHASE_ONLY_PARAMS(phase) \ __LINE__, SkOpPhase::phase, 0 #define DEBUG_PHASE_PARAMS(phase) \ , DEBUG_PHASE_ONLY_PARAMS(phase) #define DEBUG_SET_PHASE() \ this->globalState()->debugSetPhase(__func__, lineNo, phase, iteration) #define DEBUG_STATIC_SET_PHASE(obj) \ obj->globalState()->debugSetPhase(__func__, lineNo, phase, iteration) #elif DEBUG_VALIDATE #define DEBUG_COIN_DECLARE_ONLY_PARAMS() \ SkOpPhase phase #define DEBUG_COIN_DECLARE_PARAMS() \ , DEBUG_COIN_DECLARE_ONLY_PARAMS() #define DEBUG_COIN_ONLY_PARAMS() \ SkOpPhase::kNoChange #define DEBUG_COIN_PARAMS() \ , DEBUG_COIN_ONLY_PARAMS() #define DEBUG_ITER_ONLY_PARAMS(iteration) \ SkOpPhase::kNoChange #define DEBUG_ITER_PARAMS(iteration) \ , DEBUG_ITER_ONLY_PARAMS(iteration) #define DEBUG_PHASE_ONLY_PARAMS(phase) \ SkOpPhase::phase #define DEBUG_PHASE_PARAMS(phase) \ , DEBUG_PHASE_ONLY_PARAMS(phase) #define DEBUG_SET_PHASE() \ this->globalState()->debugSetPhase(phase) #define DEBUG_STATIC_SET_PHASE(obj) \ obj->globalState()->debugSetPhase(phase) #else #define DEBUG_COIN_DECLARE_ONLY_PARAMS() #define DEBUG_COIN_DECLARE_PARAMS() #define DEBUG_COIN_ONLY_PARAMS() #define DEBUG_COIN_PARAMS() #define DEBUG_ITER_ONLY_PARAMS(iteration) #define DEBUG_ITER_PARAMS(iteration) #define DEBUG_PHASE_ONLY_PARAMS(phase) #define DEBUG_PHASE_PARAMS(phase) #define DEBUG_SET_PHASE() #define DEBUG_STATIC_SET_PHASE(obj) #endif #define CUBIC_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" #define CONIC_DEBUG_STR "{{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}, %1.9g}" #define QUAD_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" #define LINE_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}" #define PT_DEBUG_STR "{{%1.9g,%1.9g}}" #define T_DEBUG_STR(t, n) #t "[" #n "]=%1.9g" #define TX_DEBUG_STR(t) #t "[%d]=%1.9g" #define CUBIC_DEBUG_DATA(c) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY #define CONIC_DEBUG_DATA(c, w) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, w #define QUAD_DEBUG_DATA(q) q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY #define LINE_DEBUG_DATA(l) l[0].fX, l[0].fY, l[1].fX, l[1].fY #define PT_DEBUG_DATA(i, n) i.pt(n).asSkPoint().fX, i.pt(n).asSkPoint().fY #ifndef DEBUG_TEST #define DEBUG_TEST 0 #endif // Tests with extreme numbers may fail, but all other tests should never fail. #define FAIL_IF(cond) \ do { bool fail = (cond); SkOPASSERT(!fail); if (fail) return false; } while (false) #define FAIL_WITH_NULL_IF(cond) \ do { bool fail = (cond); SkOPASSERT(!fail); if (fail) return nullptr; } while (false) class SkPathOpsDebug { public: #if DEBUG_COIN struct GlitchLog; enum GlitchType { kUninitialized_Glitch, kAddCorruptCoin_Glitch, kAddExpandedCoin_Glitch, kAddExpandedFail_Glitch, kAddIfCollapsed_Glitch, kAddIfMissingCoin_Glitch, kAddMissingCoin_Glitch, kAddMissingExtend_Glitch, kAddOrOverlap_Glitch, kCollapsedCoin_Glitch, kCollapsedDone_Glitch, kCollapsedOppValue_Glitch, kCollapsedSpan_Glitch, kCollapsedWindValue_Glitch, kCorrectEnd_Glitch, kDeletedCoin_Glitch, kExpandCoin_Glitch, kFail_Glitch, kMarkCoinEnd_Glitch, kMarkCoinInsert_Glitch, kMarkCoinMissing_Glitch, kMarkCoinStart_Glitch, kMergeMatches_Glitch, kMissingCoin_Glitch, kMissingDone_Glitch, kMissingIntersection_Glitch, kMoveMultiple_Glitch, kMoveNearbyClearAll_Glitch, kMoveNearbyClearAll2_Glitch, kMoveNearbyMerge_Glitch, kMoveNearbyMergeFinal_Glitch, kMoveNearbyRelease_Glitch, kMoveNearbyReleaseFinal_Glitch, kReleasedSpan_Glitch, kReturnFalse_Glitch, kUnaligned_Glitch, kUnalignedHead_Glitch, kUnalignedTail_Glitch, }; struct CoinDictEntry { int fIteration; int fLineNumber; GlitchType fGlitchType; const char* fFunctionName; }; struct CoinDict { void add(const CoinDictEntry& key); void add(const CoinDict& dict); void dump(const char* str, bool visitCheck) const; SkTDArray fDict; }; static CoinDict gCoinSumChangedDict; static CoinDict gCoinSumVisitedDict; static CoinDict gCoinVistedDict; #endif #if defined(SK_DEBUG) || !FORCE_RELEASE static int gContourID; static int gSegmentID; #endif #if DEBUG_SORT static int gSortCountDefault; static int gSortCount; #endif #if DEBUG_ACTIVE_OP static const char* kPathOpStr[]; #endif static bool gRunFail; static bool gVeryVerbose; #if DEBUG_ACTIVE_SPANS static SkString gActiveSpans; #endif #if DEBUG_DUMP_VERIFY static bool gDumpOp; static bool gVerifyOp; #endif static const char* OpStr(SkPathOp ); static void MathematicaIze(char* str, size_t bufferSize); static bool ValidWind(int winding); static void WindingPrintf(int winding); static void ShowActiveSpans(SkOpContourHead* contourList); static void ShowOnePath(const SkPath& path, const char* name, bool includeDeclaration); static void ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name); static bool ChaseContains(const SkTDArray& , const SkOpSpanBase* ); static void CheckHealth(class SkOpContourHead* contourList); #if DEBUG_COIN static void DumpCoinDict(); static void DumpGlitchType(GlitchType ); #endif }; // Visual Studio 2017 does not permit calling member functions from the Immediate Window. // Global functions work fine, however. Use globals rather than static members inside a class. const SkOpAngle* AngleAngle(const SkOpAngle*, int id); SkOpContour* AngleContour(SkOpAngle*, int id); const SkOpPtT* AnglePtT(const SkOpAngle*, int id); const SkOpSegment* AngleSegment(const SkOpAngle*, int id); const SkOpSpanBase* AngleSpan(const SkOpAngle*, int id); const SkOpAngle* ContourAngle(SkOpContour*, int id); SkOpContour* ContourContour(SkOpContour*, int id); const SkOpPtT* ContourPtT(SkOpContour*, int id); const SkOpSegment* ContourSegment(SkOpContour*, int id); const SkOpSpanBase* ContourSpan(SkOpContour*, int id); const SkOpAngle* CoincidenceAngle(SkOpCoincidence*, int id); SkOpContour* CoincidenceContour(SkOpCoincidence*, int id); const SkOpPtT* CoincidencePtT(SkOpCoincidence*, int id); const SkOpSegment* CoincidenceSegment(SkOpCoincidence*, int id); const SkOpSpanBase* CoincidenceSpan(SkOpCoincidence*, int id); const SkOpAngle* PtTAngle(const SkOpPtT*, int id); SkOpContour* PtTContour(SkOpPtT*, int id); const SkOpPtT* PtTPtT(const SkOpPtT*, int id); const SkOpSegment* PtTSegment(const SkOpPtT*, int id); const SkOpSpanBase* PtTSpan(const SkOpPtT*, int id); const SkOpAngle* SegmentAngle(const SkOpSegment*, int id); SkOpContour* SegmentContour(SkOpSegment*, int id); const SkOpPtT* SegmentPtT(const SkOpSegment*, int id); const SkOpSegment* SegmentSegment(const SkOpSegment*, int id); const SkOpSpanBase* SegmentSpan(const SkOpSegment*, int id); const SkOpAngle* SpanAngle(const SkOpSpanBase*, int id); SkOpContour* SpanContour(SkOpSpanBase*, int id); const SkOpPtT* SpanPtT(const SkOpSpanBase*, int id); const SkOpSegment* SpanSegment(const SkOpSpanBase*, int id); const SkOpSpanBase* SpanSpan(const SkOpSpanBase*, int id); #if DEBUG_DUMP_VERIFY void DumpOp(const SkPath& one, const SkPath& two, SkPathOp op, const char* testName); void DumpOp(FILE* file, const SkPath& one, const SkPath& two, SkPathOp op, const char* testName); void DumpSimplify(const SkPath& path, const char* testName); void DumpSimplify(FILE* file, const SkPath& path, const char* testName); void ReportOpFail(const SkPath& one, const SkPath& two, SkPathOp op); void ReportSimplifyFail(const SkPath& path); void VerifyOp(const SkPath& one, const SkPath& two, SkPathOp op, const SkPath& result); void VerifySimplify(const SkPath& path, const SkPath& result); #endif // global path dumps for msvs Visual Studio 17 to use from Immediate Window void Dump(const SkOpContour& ); void DumpAll(const SkOpContour& ); void DumpAngles(const SkOpContour& ); void DumpContours(const SkOpContour& ); void DumpContoursAll(const SkOpContour& ); void DumpContoursAngles(const SkOpContour& ); void DumpContoursPts(const SkOpContour& ); void DumpContoursPt(const SkOpContour& , int segmentID); void DumpContoursSegment(const SkOpContour& , int segmentID); void DumpContoursSpan(const SkOpContour& , int segmentID); void DumpContoursSpans(const SkOpContour& ); void DumpPt(const SkOpContour& , int ); void DumpPts(const SkOpContour& , const char* prefix = "seg"); void DumpSegment(const SkOpContour& , int ); void DumpSegments(const SkOpContour& , const char* prefix = "seg", SkPathOp op = (SkPathOp) -1); void DumpSpan(const SkOpContour& , int ); void DumpSpans(const SkOpContour& ); void Dump(const SkOpSegment& ); void DumpAll(const SkOpSegment& ); void DumpAngles(const SkOpSegment& ); void DumpCoin(const SkOpSegment& ); void DumpPts(const SkOpSegment& , const char* prefix = "seg"); void Dump(const SkOpPtT& ); void DumpAll(const SkOpPtT& ); void Dump(const SkOpSpanBase& ); void DumpCoin(const SkOpSpanBase& ); void DumpAll(const SkOpSpanBase& ); void DumpCoin(const SkOpSpan& ); bool DumpSpan(const SkOpSpan& ); void Dump(const SkDConic& ); void DumpID(const SkDConic& , int id); void Dump(const SkDCubic& ); void DumpID(const SkDCubic& , int id); void Dump(const SkDLine& ); void DumpID(const SkDLine& , int id); void Dump(const SkDQuad& ); void DumpID(const SkDQuad& , int id); void Dump(const SkDPoint& ); void Dump(const SkOpAngle& ); // generates tools/path_sorter.htm and path_visualizer.htm compatible data void DumpQ(const SkDQuad& quad1, const SkDQuad& quad2, int testNo); void DumpT(const SkDQuad& quad, double t); // global path dumps for msvs Visual Studio 17 to use from Immediate Window void Dump(const SkPath& path); void DumpHex(const SkPath& path); enum SkPathOpsMask { kWinding_PathOpsMask = -1, kNo_PathOpsMask = 0, kEvenOdd_PathOpsMask = 1 }; class SkArenaAlloc; class SkOpCoincidence; class SkOpContour; class SkOpContourHead; enum class SkOpPhase : char { kNoChange, kIntersecting, kWalking, kFixWinding, }; class SkOpGlobalState { public: SkOpGlobalState(SkOpContourHead* head, SkArenaAlloc* allocator SkDEBUGPARAMS(bool debugSkipAssert) SkDEBUGPARAMS(const char* testName)); enum { kMaxWindingTries = 10 }; bool allocatedOpSpan() const { return fAllocatedOpSpan; } SkArenaAlloc* allocator() { return fAllocator; } void bumpNested() { ++fNested; } void clearNested() { fNested = 0; } SkOpCoincidence* coincidence() { return fCoincidence; } SkOpContourHead* contourHead() { return fContourHead; } #ifdef SK_DEBUG const class SkOpAngle* debugAngle(int id) const; const SkOpCoincidence* debugCoincidence() const; SkOpContour* debugContour(int id) const; const class SkOpPtT* debugPtT(int id) const; #endif static bool DebugRunFail(); #ifdef SK_DEBUG const class SkOpSegment* debugSegment(int id) const; bool debugSkipAssert() const { return fDebugSkipAssert; } const class SkOpSpanBase* debugSpan(int id) const; const char* debugTestName() const { return fDebugTestName; } #endif #if DEBUG_T_SECT_LOOP_COUNT void debugAddLoopCount(SkIntersections* , const SkIntersectionHelper& , const SkIntersectionHelper& ); void debugDoYourWorst(SkOpGlobalState* ); void debugLoopReport(); void debugResetLoopCounts(); #endif #if DEBUG_COINCIDENCE void debugSetCheckHealth(bool check) { fDebugCheckHealth = check; } bool debugCheckHealth() const { return fDebugCheckHealth; } #endif #if DEBUG_VALIDATE || DEBUG_COIN void debugSetPhase(const char* funcName DEBUG_COIN_DECLARE_PARAMS()) const; #endif #if DEBUG_COIN void debugAddToCoinChangedDict(); void debugAddToGlobalCoinDicts(); SkPathOpsDebug::CoinDict* debugCoinChangedDict() { return &fCoinChangedDict; } const SkPathOpsDebug::CoinDictEntry& debugCoinDictEntry() const { return fCoinDictEntry; } static void DumpCoinDict(); #endif int nested() const { return fNested; } #ifdef SK_DEBUG int nextAngleID() { return ++fAngleID; } int nextCoinID() { return ++fCoinID; } int nextContourID() { return ++fContourID; } int nextPtTID() { return ++fPtTID; } int nextSegmentID() { return ++fSegmentID; } int nextSpanID() { return ++fSpanID; } #endif SkOpPhase phase() const { return fPhase; } void resetAllocatedOpSpan() { fAllocatedOpSpan = false; } void setAllocatedOpSpan() { fAllocatedOpSpan = true; } void setCoincidence(SkOpCoincidence* coincidence) { fCoincidence = coincidence; } void setContourHead(SkOpContourHead* contourHead) { fContourHead = contourHead; } void setPhase(SkOpPhase phase) { if (SkOpPhase::kNoChange == phase) { return; } SkASSERT(fPhase != phase); fPhase = phase; } // called in very rare cases where angles are sorted incorrectly -- signfies op will fail void setWindingFailed() { fWindingFailed = true; } bool windingFailed() const { return fWindingFailed; } private: SkArenaAlloc* fAllocator; SkOpCoincidence* fCoincidence; SkOpContourHead* fContourHead; int fNested; bool fAllocatedOpSpan; bool fWindingFailed; SkOpPhase fPhase; #ifdef SK_DEBUG const char* fDebugTestName; void* fDebugReporter; int fAngleID; int fCoinID; int fContourID; int fPtTID; int fSegmentID; int fSpanID; bool fDebugSkipAssert; #endif #if DEBUG_T_SECT_LOOP_COUNT int fDebugLoopCount[3]; SkPath::Verb fDebugWorstVerb[6]; SkPoint fDebugWorstPts[24]; float fDebugWorstWeight[6]; #endif #if DEBUG_COIN SkPathOpsDebug::CoinDict fCoinChangedDict; SkPathOpsDebug::CoinDict fCoinVisitedDict; SkPathOpsDebug::CoinDictEntry fCoinDictEntry; const char* fPreviousFuncName; #endif #if DEBUG_COINCIDENCE bool fDebugCheckHealth; #endif }; #ifdef SK_DEBUG #if DEBUG_COINCIDENCE #define SkOPASSERT(cond) SkASSERT((this->globalState() && \ (this->globalState()->debugCheckHealth() || \ this->globalState()->debugSkipAssert())) || (cond)) #else #define SkOPASSERT(cond) SkASSERT((this->globalState() && \ this->globalState()->debugSkipAssert()) || (cond)) #endif #define SkOPOBJASSERT(obj, cond) SkASSERT((obj->globalState() && \ obj->globalState()->debugSkipAssert()) || (cond)) #else #define SkOPASSERT(cond) #define SkOPOBJASSERT(obj, cond) #endif // Use Almost Equal when comparing coordinates. Use epsilon to compare T values. bool AlmostEqualUlps(float a, float b); inline bool AlmostEqualUlps(double a, double b) { return AlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool AlmostEqualUlpsNoNormalCheck(float a, float b); inline bool AlmostEqualUlpsNoNormalCheck(double a, double b) { return AlmostEqualUlpsNoNormalCheck(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool AlmostEqualUlps_Pin(float a, float b); inline bool AlmostEqualUlps_Pin(double a, double b) { return AlmostEqualUlps_Pin(SkDoubleToScalar(a), SkDoubleToScalar(b)); } // Use Almost Dequal when comparing should not special case denormalized values. bool AlmostDequalUlps(float a, float b); bool AlmostDequalUlps(double a, double b); bool NotAlmostEqualUlps(float a, float b); inline bool NotAlmostEqualUlps(double a, double b) { return NotAlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool NotAlmostEqualUlps_Pin(float a, float b); inline bool NotAlmostEqualUlps_Pin(double a, double b) { return NotAlmostEqualUlps_Pin(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool NotAlmostDequalUlps(float a, float b); inline bool NotAlmostDequalUlps(double a, double b) { return NotAlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } // Use Almost Bequal when comparing coordinates in conjunction with between. bool AlmostBequalUlps(float a, float b); inline bool AlmostBequalUlps(double a, double b) { return AlmostBequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool AlmostPequalUlps(float a, float b); inline bool AlmostPequalUlps(double a, double b) { return AlmostPequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool RoughlyEqualUlps(float a, float b); inline bool RoughlyEqualUlps(double a, double b) { return RoughlyEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool AlmostLessUlps(float a, float b); inline bool AlmostLessUlps(double a, double b) { return AlmostLessUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool AlmostLessOrEqualUlps(float a, float b); inline bool AlmostLessOrEqualUlps(double a, double b) { return AlmostLessOrEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } bool AlmostBetweenUlps(float a, float b, float c); inline bool AlmostBetweenUlps(double a, double b, double c) { return AlmostBetweenUlps(SkDoubleToScalar(a), SkDoubleToScalar(b), SkDoubleToScalar(c)); } int UlpsDistance(float a, float b); inline int UlpsDistance(double a, double b) { return UlpsDistance(SkDoubleToScalar(a), SkDoubleToScalar(b)); } // FLT_EPSILON == 1.19209290E-07 == 1 / (2 ^ 23) // DBL_EPSILON == 2.22045e-16 const double FLT_EPSILON_CUBED = FLT_EPSILON * FLT_EPSILON * FLT_EPSILON; const double FLT_EPSILON_HALF = FLT_EPSILON / 2; const double FLT_EPSILON_DOUBLE = FLT_EPSILON * 2; const double FLT_EPSILON_ORDERABLE_ERR = FLT_EPSILON * 16; const double FLT_EPSILON_SQUARED = FLT_EPSILON * FLT_EPSILON; // Use a compile-time constant for FLT_EPSILON_SQRT to avoid initializers. // A 17 digit constant guarantees exact results. const double FLT_EPSILON_SQRT = 0.00034526697709225118; // sqrt(FLT_EPSILON); const double FLT_EPSILON_INVERSE = 1 / FLT_EPSILON; const double DBL_EPSILON_ERR = DBL_EPSILON * 4; // FIXME: tune -- allow a few bits of error const double DBL_EPSILON_SUBDIVIDE_ERR = DBL_EPSILON * 16; const double ROUGH_EPSILON = FLT_EPSILON * 64; const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256; const double WAY_ROUGH_EPSILON = FLT_EPSILON * 2048; const double BUMP_EPSILON = FLT_EPSILON * 4096; const SkScalar INVERSE_NUMBER_RANGE = FLT_EPSILON_ORDERABLE_ERR; inline bool zero_or_one(double x) { return x == 0 || x == 1; } inline bool approximately_zero(double x) { return fabs(x) < FLT_EPSILON; } inline bool precisely_zero(double x) { return fabs(x) < DBL_EPSILON_ERR; } inline bool precisely_subdivide_zero(double x) { return fabs(x) < DBL_EPSILON_SUBDIVIDE_ERR; } inline bool approximately_zero(float x) { return fabs(x) < FLT_EPSILON; } inline bool approximately_zero_half(double x) { return fabs(x) < FLT_EPSILON_HALF; } inline bool approximately_zero_double(double x) { return fabs(x) < FLT_EPSILON_DOUBLE; } inline bool approximately_zero_orderable(double x) { return fabs(x) < FLT_EPSILON_ORDERABLE_ERR; } inline bool approximately_zero_squared(double x) { return fabs(x) < FLT_EPSILON_SQUARED; } inline bool approximately_zero_sqrt(double x) { return fabs(x) < FLT_EPSILON_SQRT; } inline bool roughly_zero(double x) { return fabs(x) < ROUGH_EPSILON; } inline bool approximately_zero_inverse(double x) { return fabs(x) > FLT_EPSILON_INVERSE; } inline bool approximately_zero_when_compared_to(double x, double y) { return x == 0 || fabs(x) < fabs(y * FLT_EPSILON); } inline bool precisely_zero_when_compared_to(double x, double y) { return x == 0 || fabs(x) < fabs(y * DBL_EPSILON); } inline bool roughly_zero_when_compared_to(double x, double y) { return x == 0 || fabs(x) < fabs(y * ROUGH_EPSILON); } // Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use // AlmostEqualUlps instead. inline bool approximately_equal(double x, double y) { return approximately_zero(x - y); } inline bool precisely_equal(double x, double y) { return precisely_zero(x - y); } inline bool precisely_subdivide_equal(double x, double y) { return precisely_subdivide_zero(x - y); } inline bool approximately_equal_half(double x, double y) { return approximately_zero_half(x - y); } inline bool approximately_equal_double(double x, double y) { return approximately_zero_double(x - y); } inline bool approximately_equal_orderable(double x, double y) { return approximately_zero_orderable(x - y); } inline bool approximately_equal_squared(double x, double y) { return approximately_equal(x, y); } inline bool approximately_greater(double x, double y) { return x - FLT_EPSILON >= y; } inline bool approximately_greater_double(double x, double y) { return x - FLT_EPSILON_DOUBLE >= y; } inline bool approximately_greater_orderable(double x, double y) { return x - FLT_EPSILON_ORDERABLE_ERR >= y; } inline bool approximately_greater_or_equal(double x, double y) { return x + FLT_EPSILON > y; } inline bool approximately_greater_or_equal_double(double x, double y) { return x + FLT_EPSILON_DOUBLE > y; } inline bool approximately_greater_or_equal_orderable(double x, double y) { return x + FLT_EPSILON_ORDERABLE_ERR > y; } inline bool approximately_lesser(double x, double y) { return x + FLT_EPSILON <= y; } inline bool approximately_lesser_double(double x, double y) { return x + FLT_EPSILON_DOUBLE <= y; } inline bool approximately_lesser_orderable(double x, double y) { return x + FLT_EPSILON_ORDERABLE_ERR <= y; } inline bool approximately_lesser_or_equal(double x, double y) { return x - FLT_EPSILON < y; } inline bool approximately_lesser_or_equal_double(double x, double y) { return x - FLT_EPSILON_DOUBLE < y; } inline bool approximately_lesser_or_equal_orderable(double x, double y) { return x - FLT_EPSILON_ORDERABLE_ERR < y; } inline bool approximately_greater_than_one(double x) { return x > 1 - FLT_EPSILON; } inline bool precisely_greater_than_one(double x) { return x > 1 - DBL_EPSILON_ERR; } inline bool approximately_less_than_zero(double x) { return x < FLT_EPSILON; } inline bool precisely_less_than_zero(double x) { return x < DBL_EPSILON_ERR; } inline bool approximately_negative(double x) { return x < FLT_EPSILON; } inline bool approximately_negative_orderable(double x) { return x < FLT_EPSILON_ORDERABLE_ERR; } inline bool precisely_negative(double x) { return x < DBL_EPSILON_ERR; } inline bool approximately_one_or_less(double x) { return x < 1 + FLT_EPSILON; } inline bool approximately_one_or_less_double(double x) { return x < 1 + FLT_EPSILON_DOUBLE; } inline bool approximately_positive(double x) { return x > -FLT_EPSILON; } inline bool approximately_positive_squared(double x) { return x > -(FLT_EPSILON_SQUARED); } inline bool approximately_zero_or_more(double x) { return x > -FLT_EPSILON; } inline bool approximately_zero_or_more_double(double x) { return x > -FLT_EPSILON_DOUBLE; } inline bool approximately_between_orderable(double a, double b, double c) { return a <= c ? approximately_negative_orderable(a - b) && approximately_negative_orderable(b - c) : approximately_negative_orderable(b - a) && approximately_negative_orderable(c - b); } inline bool approximately_between(double a, double b, double c) { return a <= c ? approximately_negative(a - b) && approximately_negative(b - c) : approximately_negative(b - a) && approximately_negative(c - b); } inline bool precisely_between(double a, double b, double c) { return a <= c ? precisely_negative(a - b) && precisely_negative(b - c) : precisely_negative(b - a) && precisely_negative(c - b); } // returns true if (a <= b <= c) || (a >= b >= c) inline bool between(double a, double b, double c) { SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0) || (precisely_zero(a) && precisely_zero(b) && precisely_zero(c))); return (a - b) * (c - b) <= 0; } inline bool roughly_equal(double x, double y) { return fabs(x - y) < ROUGH_EPSILON; } inline bool roughly_negative(double x) { return x < ROUGH_EPSILON; } inline bool roughly_between(double a, double b, double c) { return a <= c ? roughly_negative(a - b) && roughly_negative(b - c) : roughly_negative(b - a) && roughly_negative(c - b); } inline bool more_roughly_equal(double x, double y) { return fabs(x - y) < MORE_ROUGH_EPSILON; } inline SkPath::Verb SkPathOpsPointsToVerb(int points) { int verb = (1 << points) >> 1; #ifdef SK_DEBUG switch (points) { case 0: SkASSERT(SkPath::kMove_Verb == verb); break; case 1: SkASSERT(SkPath::kLine_Verb == verb); break; case 2: SkASSERT(SkPath::kQuad_Verb == verb); break; case 3: SkASSERT(SkPath::kCubic_Verb == verb); break; default: SkDEBUGFAIL("should not be here"); } #endif return (SkPath::Verb)verb; } inline int SkPathOpsVerbToPoints(SkPath::Verb verb) { int points = (int) verb - (((int) verb + 1) >> 2); #ifdef SK_DEBUG switch (verb) { case SkPath::kLine_Verb: SkASSERT(1 == points); break; case SkPath::kQuad_Verb: SkASSERT(2 == points); break; case SkPath::kConic_Verb: SkASSERT(2 == points); break; case SkPath::kCubic_Verb: SkASSERT(3 == points); break; default: SkDEBUGFAIL("should not get here"); } #endif return points; } inline double SkDInterp(double A, double B, double t) { return A + (B - A) * t; } /* Returns -1 if negative, 0 if zero, 1 if positive */ inline int SkDSign(double x) { return (x > 0) - (x < 0); } /* Returns 0 if negative, 1 if zero, 2 if positive */ inline int SKDSide(double x) { return (x > 0) + (x >= 0); } /* Returns 1 if negative, 2 if zero, 4 if positive */ inline int SkDSideBit(double x) { return 1 << SKDSide(x); } inline double SkPinT(double t) { return precisely_less_than_zero(t) ? 0 : precisely_greater_than_one(t) ? 1 : t; } inline bool AlmostEqualUlps(const SkPoint& pt1, const SkPoint& pt2) { return AlmostEqualUlps(pt1.fX, pt2.fX) && AlmostEqualUlps(pt1.fY, pt2.fY); } struct SkDVector { double fX; double fY; SkDVector& set(const SkVector& pt) { fX = pt.fX; fY = pt.fY; return *this; } // only used by testing void operator+=(const SkDVector& v) { fX += v.fX; fY += v.fY; } // only called by nearestT, which is currently only used by testing void operator-=(const SkDVector& v) { fX -= v.fX; fY -= v.fY; } // only used by testing void operator/=(const double s) { fX /= s; fY /= s; } // only used by testing void operator*=(const double s) { fX *= s; fY *= s; } SkVector asSkVector() const { SkVector v = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; return v; } // only used by testing double cross(const SkDVector& a) const { return fX * a.fY - fY * a.fX; } // similar to cross, this bastardization considers nearly coincident to be zero // uses ulps epsilon == 16 double crossCheck(const SkDVector& a) const { double xy = fX * a.fY; double yx = fY * a.fX; return AlmostEqualUlps(xy, yx) ? 0 : xy - yx; } // allow tinier numbers double crossNoNormalCheck(const SkDVector& a) const { double xy = fX * a.fY; double yx = fY * a.fX; return AlmostEqualUlpsNoNormalCheck(xy, yx) ? 0 : xy - yx; } double dot(const SkDVector& a) const { return fX * a.fX + fY * a.fY; } double length() const { return sqrt(lengthSquared()); } double lengthSquared() const { return fX * fX + fY * fY; } SkDVector& normalize() { double inverseLength = sk_ieee_double_divide(1, this->length()); fX *= inverseLength; fY *= inverseLength; return *this; } bool isFinite() const { return std::isfinite(fX) && std::isfinite(fY); } }; struct SkDPoint { double fX; double fY; void set(const SkPoint& pt) { fX = pt.fX; fY = pt.fY; } friend SkDVector operator-(const SkDPoint& a, const SkDPoint& b) { return { a.fX - b.fX, a.fY - b.fY }; } friend bool operator==(const SkDPoint& a, const SkDPoint& b) { return a.fX == b.fX && a.fY == b.fY; } friend bool operator!=(const SkDPoint& a, const SkDPoint& b) { return a.fX != b.fX || a.fY != b.fY; } void operator=(const SkPoint& pt) { fX = pt.fX; fY = pt.fY; } // only used by testing void operator+=(const SkDVector& v) { fX += v.fX; fY += v.fY; } // only used by testing void operator-=(const SkDVector& v) { fX -= v.fX; fY -= v.fY; } // only used by testing SkDPoint operator+(const SkDVector& v) { SkDPoint result = *this; result += v; return result; } // only used by testing SkDPoint operator-(const SkDVector& v) { SkDPoint result = *this; result -= v; return result; } // note: this can not be implemented with // return approximately_equal(a.fY, fY) && approximately_equal(a.fX, fX); // because that will not take the magnitude of the values into account bool approximatelyDEqual(const SkDPoint& a) const { if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { return true; } if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { return false; } double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? double tiniest = std::min(std::min(std::min(fX, a.fX), fY), a.fY); double largest = std::max(std::max(std::max(fX, a.fX), fY), a.fY); largest = std::max(largest, -tiniest); return AlmostDequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? } bool approximatelyDEqual(const SkPoint& a) const { SkDPoint dA; dA.set(a); return approximatelyDEqual(dA); } bool approximatelyEqual(const SkDPoint& a) const { if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { return true; } if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { return false; } double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? double tiniest = std::min(std::min(std::min(fX, a.fX), fY), a.fY); double largest = std::max(std::max(std::max(fX, a.fX), fY), a.fY); largest = std::max(largest, -tiniest); return AlmostPequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? } bool approximatelyEqual(const SkPoint& a) const { SkDPoint dA; dA.set(a); return approximatelyEqual(dA); } static bool ApproximatelyEqual(const SkPoint& a, const SkPoint& b) { if (approximately_equal(a.fX, b.fX) && approximately_equal(a.fY, b.fY)) { return true; } if (!RoughlyEqualUlps(a.fX, b.fX) || !RoughlyEqualUlps(a.fY, b.fY)) { return false; } SkDPoint dA, dB; dA.set(a); dB.set(b); double dist = dA.distance(dB); // OPTIMIZATION: can we compare against distSq instead ? float tiniest = std::min(std::min(std::min(a.fX, b.fX), a.fY), b.fY); float largest = std::max(std::max(std::max(a.fX, b.fX), a.fY), b.fY); largest = std::max(largest, -tiniest); return AlmostDequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance? } // only used by testing bool approximatelyZero() const { return approximately_zero(fX) && approximately_zero(fY); } SkPoint asSkPoint() const { SkPoint pt = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; return pt; } double distance(const SkDPoint& a) const { SkDVector temp = *this - a; return temp.length(); } double distanceSquared(const SkDPoint& a) const { SkDVector temp = *this - a; return temp.lengthSquared(); } static SkDPoint Mid(const SkDPoint& a, const SkDPoint& b) { SkDPoint result; result.fX = (a.fX + b.fX) / 2; result.fY = (a.fY + b.fY) / 2; return result; } bool roughlyEqual(const SkDPoint& a) const { if (roughly_equal(fX, a.fX) && roughly_equal(fY, a.fY)) { return true; } double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? double tiniest = std::min(std::min(std::min(fX, a.fX), fY), a.fY); double largest = std::max(std::max(std::max(fX, a.fX), fY), a.fY); largest = std::max(largest, -tiniest); return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? } static bool RoughlyEqual(const SkPoint& a, const SkPoint& b) { if (!RoughlyEqualUlps(a.fX, b.fX) && !RoughlyEqualUlps(a.fY, b.fY)) { return false; } SkDPoint dA, dB; dA.set(a); dB.set(b); double dist = dA.distance(dB); // OPTIMIZATION: can we compare against distSq instead ? float tiniest = std::min(std::min(std::min(a.fX, b.fX), a.fY), b.fY); float largest = std::max(std::max(std::max(a.fX, b.fX), a.fY), b.fY); largest = std::max(largest, -tiniest); return RoughlyEqualUlps((double) largest, largest + dist); // is dist within ULPS tolerance? } // very light weight check, should only be used for inequality check static bool WayRoughlyEqual(const SkPoint& a, const SkPoint& b) { float largestNumber = std::max(SkTAbs(a.fX), std::max(SkTAbs(a.fY), std::max(SkTAbs(b.fX), SkTAbs(b.fY)))); SkVector diffs = a - b; float largestDiff = std::max(diffs.fX, diffs.fY); return roughly_zero_when_compared_to(largestDiff, largestNumber); } // utilities callable by the user from the debugger when the implementation code is linked in void dump() const; static void Dump(const SkPoint& pt); static void DumpHex(const SkPoint& pt); }; class SkArenaAlloc; class SkIntersections; struct SkDRect; class SkTCurve { public: virtual ~SkTCurve() {} virtual const SkDPoint& operator[](int n) const = 0; virtual SkDPoint& operator[](int n) = 0; virtual bool collapsed() const = 0; virtual bool controlsInside() const = 0; virtual void debugInit() = 0; #if DEBUG_T_SECT virtual void dumpID(int id) const = 0; #endif virtual SkDVector dxdyAtT(double t) const = 0; virtual bool hullIntersects(const SkDQuad& , bool* isLinear) const = 0; virtual bool hullIntersects(const SkDConic& , bool* isLinear) const = 0; virtual bool hullIntersects(const SkDCubic& , bool* isLinear) const = 0; virtual bool hullIntersects(const SkTCurve& , bool* isLinear) const = 0; virtual int intersectRay(SkIntersections* i, const SkDLine& line) const = 0; virtual bool IsConic() const = 0; virtual SkTCurve* make(SkArenaAlloc& ) const = 0; virtual int maxIntersections() const = 0; virtual void otherPts(int oddMan, const SkDPoint* endPt[2]) const = 0; virtual int pointCount() const = 0; virtual int pointLast() const = 0; virtual SkDPoint ptAtT(double t) const = 0; virtual void setBounds(SkDRect* ) const = 0; virtual void subDivide(double t1, double t2, SkTCurve* curve) const = 0; #ifdef SK_DEBUG virtual SkOpGlobalState* globalState() const = 0; #endif }; class SkIntersections; class SkOpGlobalState; struct SkDConic; struct SkDCubicPair; struct SkDLine; struct SkDQuad; struct SkDRect; struct SkDCubic { static const int kPointCount = 4; static const int kPointLast = kPointCount - 1; static const int kMaxIntersections = 9; enum SearchAxis { kXAxis, kYAxis }; bool collapsed() const { return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]) && fPts[0].approximatelyEqual(fPts[3]); } bool controlsInside() const { SkDVector v01 = fPts[0] - fPts[1]; SkDVector v02 = fPts[0] - fPts[2]; SkDVector v03 = fPts[0] - fPts[3]; SkDVector v13 = fPts[1] - fPts[3]; SkDVector v23 = fPts[2] - fPts[3]; return v03.dot(v01) > 0 && v03.dot(v02) > 0 && v03.dot(v13) > 0 && v03.dot(v23) > 0; } static bool IsConic() { return false; } const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } void align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const; double binarySearch(double min, double max, double axisIntercept, SearchAxis xAxis) const; double calcPrecision() const; SkDCubicPair chopAt(double t) const; static void Coefficients(const double* cubic, double* A, double* B, double* C, double* D); static int ComplexBreak(const SkPoint pts[4], SkScalar* t); int convexHull(char order[kPointCount]) const; void debugInit() { sk_bzero(fPts, sizeof(fPts)); } void debugSet(const SkDPoint* pts); void dump() const; // callable from the debugger when the implementation code is linked in void dumpID(int id) const; void dumpInner() const; SkDVector dxdyAtT(double t) const; bool endsAreExtremaInXOrY() const; static int FindExtrema(const double src[], double tValue[2]); int findInflections(double tValues[2]) const; static int FindInflections(const SkPoint a[kPointCount], double tValues[2]) { SkDCubic cubic; return cubic.set(a).findInflections(tValues); } int findMaxCurvature(double tValues[]) const; #ifdef SK_DEBUG SkOpGlobalState* globalState() const { return fDebugGlobalState; } #endif bool hullIntersects(const SkDCubic& c2, bool* isLinear) const; bool hullIntersects(const SkDConic& c, bool* isLinear) const; bool hullIntersects(const SkDQuad& c2, bool* isLinear) const; bool hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const; bool isLinear(int startIndex, int endIndex) const; static int maxIntersections() { return kMaxIntersections; } bool monotonicInX() const; bool monotonicInY() const; void otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const; static int pointCount() { return kPointCount; } static int pointLast() { return kPointLast; } SkDPoint ptAtT(double t) const; static int RootsReal(double A, double B, double C, double D, double t[3]); static int RootsValidT(const double A, const double B, const double C, double D, double s[3]); int searchRoots(double extremes[6], int extrema, double axisIntercept, SearchAxis xAxis, double* validRoots) const; bool toFloatPoints(SkPoint* ) const; /** * Return the number of valid roots (0 < root < 1) for this cubic intersecting the * specified horizontal line. */ int horizontalIntersect(double yIntercept, double roots[3]) const; /** * Return the number of valid roots (0 < root < 1) for this cubic intersecting the * specified vertical line. */ int verticalIntersect(double xIntercept, double roots[3]) const; // add debug only global pointer so asserts can be skipped by fuzzers const SkDCubic& set(const SkPoint pts[kPointCount] SkDEBUGPARAMS(SkOpGlobalState* state = nullptr)) { fPts[0] = pts[0]; fPts[1] = pts[1]; fPts[2] = pts[2]; fPts[3] = pts[3]; SkDEBUGCODE(fDebugGlobalState = state); return *this; } SkDCubic subDivide(double t1, double t2) const; void subDivide(double t1, double t2, SkDCubic* c) const { *c = this->subDivide(t1, t2); } static SkDCubic SubDivide(const SkPoint a[kPointCount], double t1, double t2) { SkDCubic cubic; return cubic.set(a).subDivide(t1, t2); } void subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) const; static void SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) { SkDCubic cubic; cubic.set(pts).subDivide(a, d, t1, t2, p); } double top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const; SkDQuad toQuad() const; static const int gPrecisionUnit; SkDPoint fPts[kPointCount]; SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); }; /* Given the set [0, 1, 2, 3], and two of the four members, compute an XOR mask that computes the other two. Note that: one ^ two == 3 for (0, 3), (1, 2) one ^ two < 3 for (0, 1), (0, 2), (1, 3), (2, 3) 3 - (one ^ two) is either 0, 1, or 2 1 >> (3 - (one ^ two)) is either 0 or 1 thus: returned == 2 for (0, 3), (1, 2) returned == 3 for (0, 1), (0, 2), (1, 3), (2, 3) given that: (0, 3) ^ 2 -> (2, 1) (1, 2) ^ 2 -> (3, 0) (0, 1) ^ 3 -> (3, 2) (0, 2) ^ 3 -> (3, 1) (1, 3) ^ 3 -> (2, 0) (2, 3) ^ 3 -> (1, 0) */ inline int other_two(int one, int two) { return 1 >> (3 - (one ^ two)) ^ 3; } struct SkDCubicPair { SkDCubic first() const { #ifdef SK_DEBUG SkDCubic result; result.debugSet(&pts[0]); return result; #else return (const SkDCubic&) pts[0]; #endif } SkDCubic second() const { #ifdef SK_DEBUG SkDCubic result; result.debugSet(&pts[3]); return result; #else return (const SkDCubic&) pts[3]; #endif } SkDPoint pts[7]; }; class SkTCubic : public SkTCurve { public: SkDCubic fCubic; SkTCubic() {} SkTCubic(const SkDCubic& c) : fCubic(c) { } ~SkTCubic() override {} const SkDPoint& operator[](int n) const override { return fCubic[n]; } SkDPoint& operator[](int n) override { return fCubic[n]; } bool collapsed() const override { return fCubic.collapsed(); } bool controlsInside() const override { return fCubic.controlsInside(); } void debugInit() override { return fCubic.debugInit(); } #if DEBUG_T_SECT void dumpID(int id) const override { return fCubic.dumpID(id); } #endif SkDVector dxdyAtT(double t) const override { return fCubic.dxdyAtT(t); } #ifdef SK_DEBUG SkOpGlobalState* globalState() const override { return fCubic.globalState(); } #endif bool hullIntersects(const SkDQuad& quad, bool* isLinear) const override; bool hullIntersects(const SkDConic& conic, bool* isLinear) const override; bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const override { return cubic.hullIntersects(fCubic, isLinear); } bool hullIntersects(const SkTCurve& curve, bool* isLinear) const override { return curve.hullIntersects(fCubic, isLinear); } int intersectRay(SkIntersections* i, const SkDLine& line) const override; bool IsConic() const override { return false; } SkTCurve* make(SkArenaAlloc& heap) const override { return heap.make(); } int maxIntersections() const override { return SkDCubic::kMaxIntersections; } void otherPts(int oddMan, const SkDPoint* endPt[2]) const override { fCubic.otherPts(oddMan, endPt); } int pointCount() const override { return SkDCubic::kPointCount; } int pointLast() const override { return SkDCubic::kPointLast; } SkDPoint ptAtT(double t) const override { return fCubic.ptAtT(t); } void setBounds(SkDRect* ) const override; void subDivide(double t1, double t2, SkTCurve* curve) const override { ((SkTCubic*) curve)->fCubic = fCubic.subDivide(t1, t2); } }; struct SkDLine { SkDPoint fPts[2]; const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 2); return fPts[n]; } SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 2); return fPts[n]; } const SkDLine& set(const SkPoint pts[2]) { fPts[0] = pts[0]; fPts[1] = pts[1]; return *this; } double exactPoint(const SkDPoint& xy) const; static double ExactPointH(const SkDPoint& xy, double left, double right, double y); static double ExactPointV(const SkDPoint& xy, double top, double bottom, double x); double nearPoint(const SkDPoint& xy, bool* unequal) const; bool nearRay(const SkDPoint& xy) const; static double NearPointH(const SkDPoint& xy, double left, double right, double y); static double NearPointV(const SkDPoint& xy, double top, double bottom, double x); SkDPoint ptAtT(double t) const; void dump() const; void dumpID(int ) const; void dumpInner() const; }; class SkIntersections; class SkOpGlobalState; struct SkDConic; struct SkDLine; struct SkDQuad; struct SkDRect; struct SkDQuadPair { const SkDQuad& first() const { return (const SkDQuad&) pts[0]; } const SkDQuad& second() const { return (const SkDQuad&) pts[2]; } SkDPoint pts[5]; }; struct SkDQuad { static const int kPointCount = 3; static const int kPointLast = kPointCount - 1; static const int kMaxIntersections = 4; SkDPoint fPts[kPointCount]; bool collapsed() const { return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]); } bool controlsInside() const { SkDVector v01 = fPts[0] - fPts[1]; SkDVector v02 = fPts[0] - fPts[2]; SkDVector v12 = fPts[1] - fPts[2]; return v02.dot(v01) > 0 && v02.dot(v12) > 0; } void debugInit() { sk_bzero(fPts, sizeof(fPts)); } void debugSet(const SkDPoint* pts); SkDQuad flip() const { SkDQuad result = {{fPts[2], fPts[1], fPts[0]} SkDEBUGPARAMS(fDebugGlobalState) }; return result; } static bool IsConic() { return false; } const SkDQuad& set(const SkPoint pts[kPointCount] SkDEBUGPARAMS(SkOpGlobalState* state = nullptr)) { fPts[0] = pts[0]; fPts[1] = pts[1]; fPts[2] = pts[2]; SkDEBUGCODE(fDebugGlobalState = state); return *this; } const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } static int AddValidTs(double s[], int realRoots, double* t); void align(int endIndex, SkDPoint* dstPt) const; SkDQuadPair chopAt(double t) const; SkDVector dxdyAtT(double t) const; static int FindExtrema(const double src[], double tValue[1]); #ifdef SK_DEBUG SkOpGlobalState* globalState() const { return fDebugGlobalState; } #endif /** * Return the number of valid roots (0 < root < 1) for this cubic intersecting the * specified horizontal line. */ int horizontalIntersect(double yIntercept, double roots[2]) const; bool hullIntersects(const SkDQuad& , bool* isLinear) const; bool hullIntersects(const SkDConic& , bool* isLinear) const; bool hullIntersects(const SkDCubic& , bool* isLinear) const; bool isLinear(int startIndex, int endIndex) const; static int maxIntersections() { return kMaxIntersections; } bool monotonicInX() const; bool monotonicInY() const; void otherPts(int oddMan, const SkDPoint* endPt[2]) const; static int pointCount() { return kPointCount; } static int pointLast() { return kPointLast; } SkDPoint ptAtT(double t) const; static int RootsReal(double A, double B, double C, double t[2]); static int RootsValidT(const double A, const double B, const double C, double s[2]); static void SetABC(const double* quad, double* a, double* b, double* c); SkDQuad subDivide(double t1, double t2) const; void subDivide(double t1, double t2, SkDQuad* quad) const { *quad = this->subDivide(t1, t2); } static SkDQuad SubDivide(const SkPoint a[kPointCount], double t1, double t2) { SkDQuad quad; quad.set(a); return quad.subDivide(t1, t2); } SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const; static SkDPoint SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& c, double t1, double t2) { SkDQuad quad; quad.set(pts); return quad.subDivide(a, c, t1, t2); } /** * Return the number of valid roots (0 < root < 1) for this cubic intersecting the * specified vertical line. */ int verticalIntersect(double xIntercept, double roots[2]) const; SkDCubic debugToCubic() const; // utilities callable by the user from the debugger when the implementation code is linked in void dump() const; void dumpID(int id) const; void dumpInner() const; SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); }; class SkTQuad : public SkTCurve { public: SkDQuad fQuad; SkTQuad() {} SkTQuad(const SkDQuad& q) : fQuad(q) { } ~SkTQuad() override {} const SkDPoint& operator[](int n) const override { return fQuad[n]; } SkDPoint& operator[](int n) override { return fQuad[n]; } bool collapsed() const override { return fQuad.collapsed(); } bool controlsInside() const override { return fQuad.controlsInside(); } void debugInit() override { return fQuad.debugInit(); } #if DEBUG_T_SECT void dumpID(int id) const override { return fQuad.dumpID(id); } #endif SkDVector dxdyAtT(double t) const override { return fQuad.dxdyAtT(t); } #ifdef SK_DEBUG SkOpGlobalState* globalState() const override { return fQuad.globalState(); } #endif bool hullIntersects(const SkDQuad& quad, bool* isLinear) const override { return quad.hullIntersects(fQuad, isLinear); } bool hullIntersects(const SkDConic& conic, bool* isLinear) const override; bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const override; bool hullIntersects(const SkTCurve& curve, bool* isLinear) const override { return curve.hullIntersects(fQuad, isLinear); } int intersectRay(SkIntersections* i, const SkDLine& line) const override; bool IsConic() const override { return false; } SkTCurve* make(SkArenaAlloc& heap) const override { return heap.make(); } int maxIntersections() const override { return SkDQuad::kMaxIntersections; } void otherPts(int oddMan, const SkDPoint* endPt[2]) const override { fQuad.otherPts(oddMan, endPt); } int pointCount() const override { return SkDQuad::kPointCount; } int pointLast() const override { return SkDQuad::kPointLast; } SkDPoint ptAtT(double t) const override { return fQuad.ptAtT(t); } void setBounds(SkDRect* ) const override; void subDivide(double t1, double t2, SkTCurve* curve) const override { ((SkTQuad*) curve)->fQuad = fQuad.subDivide(t1, t2); } }; // Sources // computer-aided design - volume 22 number 9 november 1990 pp 538 - 549 // online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf // This turns a line segment into a parameterized line, of the form // ax + by + c = 0 // When a^2 + b^2 == 1, the line is normalized. // The distance to the line for (x, y) is d(x,y) = ax + by + c // // Note that the distances below are not necessarily normalized. To get the true // distance, it's necessary to either call normalize() after xxxEndPoints(), or // divide the result of xxxDistance() by sqrt(normalSquared()) class SkLineParameters { public: bool cubicEndPoints(const SkDCubic& pts) { int endIndex = 1; cubicEndPoints(pts, 0, endIndex); if (dy() != 0) { return true; } if (dx() == 0) { cubicEndPoints(pts, 0, ++endIndex); SkASSERT(endIndex == 2); if (dy() != 0) { return true; } if (dx() == 0) { cubicEndPoints(pts, 0, ++endIndex); // line SkASSERT(endIndex == 3); return false; } } // FIXME: after switching to round sort, remove bumping fA if (dx() < 0) { // only worry about y bias when breaking cw/ccw tie return true; } // if cubic tangent is on x axis, look at next control point to break tie // control point may be approximate, so it must move significantly to account for error if (NotAlmostEqualUlps(pts[0].fY, pts[++endIndex].fY)) { if (pts[0].fY > pts[endIndex].fY) { fA = DBL_EPSILON; // push it from 0 to slightly negative (y() returns -a) } return true; } if (endIndex == 3) { return true; } SkASSERT(endIndex == 2); if (pts[0].fY > pts[3].fY) { fA = DBL_EPSILON; // push it from 0 to slightly negative (y() returns -a) } return true; } void cubicEndPoints(const SkDCubic& pts, int s, int e) { fA = pts[s].fY - pts[e].fY; fB = pts[e].fX - pts[s].fX; fC = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; } double cubicPart(const SkDCubic& part) { cubicEndPoints(part); if (part[0] == part[1] || ((const SkDLine& ) part[0]).nearRay(part[2])) { return pointDistance(part[3]); } return pointDistance(part[2]); } void lineEndPoints(const SkDLine& pts) { fA = pts[0].fY - pts[1].fY; fB = pts[1].fX - pts[0].fX; fC = pts[0].fX * pts[1].fY - pts[1].fX * pts[0].fY; } bool quadEndPoints(const SkDQuad& pts) { quadEndPoints(pts, 0, 1); if (dy() != 0) { return true; } if (dx() == 0) { quadEndPoints(pts, 0, 2); return false; } if (dx() < 0) { // only worry about y bias when breaking cw/ccw tie return true; } // FIXME: after switching to round sort, remove this if (pts[0].fY > pts[2].fY) { fA = DBL_EPSILON; } return true; } void quadEndPoints(const SkDQuad& pts, int s, int e) { fA = pts[s].fY - pts[e].fY; fB = pts[e].fX - pts[s].fX; fC = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; } double quadPart(const SkDQuad& part) { quadEndPoints(part); return pointDistance(part[2]); } double normalSquared() const { return fA * fA + fB * fB; } bool normalize() { double normal = sqrt(normalSquared()); if (approximately_zero(normal)) { fA = fB = fC = 0; return false; } double reciprocal = 1 / normal; fA *= reciprocal; fB *= reciprocal; fC *= reciprocal; return true; } void cubicDistanceY(const SkDCubic& pts, SkDCubic& distance) const { double oneThird = 1 / 3.0; for (int index = 0; index < 4; ++index) { distance[index].fX = index * oneThird; distance[index].fY = fA * pts[index].fX + fB * pts[index].fY + fC; } } void quadDistanceY(const SkDQuad& pts, SkDQuad& distance) const { double oneHalf = 1 / 2.0; for (int index = 0; index < 3; ++index) { distance[index].fX = index * oneHalf; distance[index].fY = fA * pts[index].fX + fB * pts[index].fY + fC; } } double controlPtDistance(const SkDCubic& pts, int index) const { SkASSERT(index == 1 || index == 2); return fA * pts[index].fX + fB * pts[index].fY + fC; } double controlPtDistance(const SkDQuad& pts) const { return fA * pts[1].fX + fB * pts[1].fY + fC; } double pointDistance(const SkDPoint& pt) const { return fA * pt.fX + fB * pt.fY + fC; } double dx() const { return fB; } double dy() const { return -fA; } private: double fA; double fB; double fC; }; class SkIntersections; class SkOpGlobalState; struct SkDCubic; struct SkDLine; struct SkDRect; struct SkDConic { static const int kPointCount = 3; static const int kPointLast = kPointCount - 1; static const int kMaxIntersections = 4; SkDQuad fPts; SkScalar fWeight; bool collapsed() const { return fPts.collapsed(); } bool controlsInside() const { return fPts.controlsInside(); } void debugInit() { fPts.debugInit(); fWeight = 0; } void debugSet(const SkDPoint* pts, SkScalar weight); SkDConic flip() const { SkDConic result = {{{fPts[2], fPts[1], fPts[0]} SkDEBUGPARAMS(fPts.fDebugGlobalState) }, fWeight}; return result; } #ifdef SK_DEBUG SkOpGlobalState* globalState() const { return fPts.globalState(); } #endif static bool IsConic() { return true; } const SkDConic& set(const SkPoint pts[kPointCount], SkScalar weight SkDEBUGPARAMS(SkOpGlobalState* state = nullptr)) { fPts.set(pts SkDEBUGPARAMS(state)); fWeight = weight; return *this; } const SkDPoint& operator[](int n) const { return fPts[n]; } SkDPoint& operator[](int n) { return fPts[n]; } static int AddValidTs(double s[], int realRoots, double* t) { return SkDQuad::AddValidTs(s, realRoots, t); } void align(int endIndex, SkDPoint* dstPt) const { fPts.align(endIndex, dstPt); } SkDVector dxdyAtT(double t) const; static int FindExtrema(const double src[], SkScalar weight, double tValue[1]); bool hullIntersects(const SkDQuad& quad, bool* isLinear) const { return fPts.hullIntersects(quad, isLinear); } bool hullIntersects(const SkDConic& conic, bool* isLinear) const { return fPts.hullIntersects(conic.fPts, isLinear); } bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const; bool isLinear(int startIndex, int endIndex) const { return fPts.isLinear(startIndex, endIndex); } static int maxIntersections() { return kMaxIntersections; } bool monotonicInX() const { return fPts.monotonicInX(); } bool monotonicInY() const { return fPts.monotonicInY(); } void otherPts(int oddMan, const SkDPoint* endPt[2]) const { fPts.otherPts(oddMan, endPt); } static int pointCount() { return kPointCount; } static int pointLast() { return kPointLast; } SkDPoint ptAtT(double t) const; static int RootsReal(double A, double B, double C, double t[2]) { return SkDQuad::RootsReal(A, B, C, t); } static int RootsValidT(const double A, const double B, const double C, double s[2]) { return SkDQuad::RootsValidT(A, B, C, s); } SkDConic subDivide(double t1, double t2) const; void subDivide(double t1, double t2, SkDConic* c) const { *c = this->subDivide(t1, t2); } static SkDConic SubDivide(const SkPoint a[kPointCount], SkScalar weight, double t1, double t2) { SkDConic conic; conic.set(a, weight); return conic.subDivide(t1, t2); } SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2, SkScalar* weight) const; static SkDPoint SubDivide(const SkPoint pts[kPointCount], SkScalar weight, const SkDPoint& a, const SkDPoint& c, double t1, double t2, SkScalar* newWeight) { SkDConic conic; conic.set(pts, weight); return conic.subDivide(a, c, t1, t2, newWeight); } // utilities callable by the user from the debugger when the implementation code is linked in void dump() const; void dumpID(int id) const; void dumpInner() const; }; class SkTConic : public SkTCurve { public: SkDConic fConic; SkTConic() {} SkTConic(const SkDConic& c) : fConic(c) { } ~SkTConic() override {} const SkDPoint& operator[](int n) const override { return fConic[n]; } SkDPoint& operator[](int n) override { return fConic[n]; } bool collapsed() const override { return fConic.collapsed(); } bool controlsInside() const override { return fConic.controlsInside(); } void debugInit() override { return fConic.debugInit(); } #if DEBUG_T_SECT void dumpID(int id) const override { return fConic.dumpID(id); } #endif SkDVector dxdyAtT(double t) const override { return fConic.dxdyAtT(t); } #ifdef SK_DEBUG SkOpGlobalState* globalState() const override { return fConic.globalState(); } #endif bool hullIntersects(const SkDQuad& quad, bool* isLinear) const override; bool hullIntersects(const SkDConic& conic, bool* isLinear) const override { return conic.hullIntersects(fConic, isLinear); } bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const override; bool hullIntersects(const SkTCurve& curve, bool* isLinear) const override { return curve.hullIntersects(fConic, isLinear); } int intersectRay(SkIntersections* i, const SkDLine& line) const override; bool IsConic() const override { return true; } SkTCurve* make(SkArenaAlloc& heap) const override { return heap.make(); } int maxIntersections() const override { return SkDConic::kMaxIntersections; } void otherPts(int oddMan, const SkDPoint* endPt[2]) const override { fConic.otherPts(oddMan, endPt); } int pointCount() const override { return SkDConic::kPointCount; } int pointLast() const override { return SkDConic::kPointLast; } SkDPoint ptAtT(double t) const override { return fConic.ptAtT(t); } void setBounds(SkDRect* ) const override; void subDivide(double t1, double t2, SkTCurve* curve) const override { ((SkTConic*) curve)->fConic = fConic.subDivide(t1, t2); } }; struct SkDRect; class SkIntersections { public: SkIntersections(SkDEBUGCODE(SkOpGlobalState* globalState = nullptr)) : fSwap(0) #ifdef SK_DEBUG SkDEBUGPARAMS(fDebugGlobalState(globalState)) , fDepth(0) #endif { sk_bzero(fPt, sizeof(fPt)); sk_bzero(fPt2, sizeof(fPt2)); sk_bzero(fT, sizeof(fT)); sk_bzero(fNearlySame, sizeof(fNearlySame)); #if DEBUG_T_SECT_LOOP_COUNT sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); #endif reset(); fMax = 0; // require that the caller set the max } class TArray { public: explicit TArray(const double ts[10]) : fTArray(ts) {} double operator[](int n) const { return fTArray[n]; } const double* fTArray; }; TArray operator[](int n) const { return TArray(fT[n]); } void allowNear(bool nearAllowed) { fAllowNear = nearAllowed; } void clearCoincidence(int index) { SkASSERT(index >= 0); int bit = 1 << index; fIsCoincident[0] &= ~bit; fIsCoincident[1] &= ~bit; } int conicHorizontal(const SkPoint a[3], SkScalar weight, SkScalar left, SkScalar right, SkScalar y, bool flipped) { SkDConic conic; conic.set(a, weight); fMax = 2; return horizontal(conic, left, right, y, flipped); } int conicVertical(const SkPoint a[3], SkScalar weight, SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { SkDConic conic; conic.set(a, weight); fMax = 2; return vertical(conic, top, bottom, x, flipped); } int conicLine(const SkPoint a[3], SkScalar weight, const SkPoint b[2]) { SkDConic conic; conic.set(a, weight); SkDLine line; line.set(b); fMax = 3; // 2; permit small coincident segment + non-coincident intersection return intersect(conic, line); } int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y, bool flipped) { SkDCubic cubic; cubic.set(a); fMax = 3; return horizontal(cubic, left, right, y, flipped); } int cubicVertical(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { SkDCubic cubic; cubic.set(a); fMax = 3; return vertical(cubic, top, bottom, x, flipped); } int cubicLine(const SkPoint a[4], const SkPoint b[2]) { SkDCubic cubic; cubic.set(a); SkDLine line; line.set(b); fMax = 3; return intersect(cubic, line); } #ifdef SK_DEBUG SkOpGlobalState* globalState() const { return fDebugGlobalState; } #endif bool hasT(double t) const { SkASSERT(t == 0 || t == 1); return fUsed > 0 && (t == 0 ? fT[0][0] == 0 : fT[0][fUsed - 1] == 1); } bool hasOppT(double t) const { SkASSERT(t == 0 || t == 1); return fUsed > 0 && (fT[1][0] == t || fT[1][fUsed - 1] == t); } int insertSwap(double one, double two, const SkDPoint& pt) { if (fSwap) { return insert(two, one, pt); } else { return insert(one, two, pt); } } bool isCoincident(int index) { return (fIsCoincident[0] & 1 << index) != 0; } int lineHorizontal(const SkPoint a[2], SkScalar left, SkScalar right, SkScalar y, bool flipped) { SkDLine line; line.set(a); fMax = 2; return horizontal(line, left, right, y, flipped); } int lineVertical(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { SkDLine line; line.set(a); fMax = 2; return vertical(line, top, bottom, x, flipped); } int lineLine(const SkPoint a[2], const SkPoint b[2]) { SkDLine aLine, bLine; aLine.set(a); bLine.set(b); fMax = 2; return intersect(aLine, bLine); } bool nearlySame(int index) const { SkASSERT(index == 0 || index == 1); return fNearlySame[index]; } const SkDPoint& pt(int index) const { return fPt[index]; } const SkDPoint& pt2(int index) const { return fPt2[index]; } int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y, bool flipped) { SkDQuad quad; quad.set(a); fMax = 2; return horizontal(quad, left, right, y, flipped); } int quadVertical(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { SkDQuad quad; quad.set(a); fMax = 2; return vertical(quad, top, bottom, x, flipped); } int quadLine(const SkPoint a[3], const SkPoint b[2]) { SkDQuad quad; quad.set(a); SkDLine line; line.set(b); return intersect(quad, line); } // leaves swap, max alone void reset() { fAllowNear = true; fUsed = 0; sk_bzero(fIsCoincident, sizeof(fIsCoincident)); } void set(bool swap, int tIndex, double t) { fT[(int) swap][tIndex] = t; } void setMax(int max) { SkASSERT(max <= (int) std::size(fPt)); fMax = max; } void swap() { fSwap ^= true; } bool swapped() const { return fSwap; } int used() const { return fUsed; } void downDepth() { SkASSERT(--fDepth >= 0); } bool unBumpT(int index) { SkASSERT(fUsed == 1); fT[0][index] = fT[0][index] * (1 + BUMP_EPSILON * 2) - BUMP_EPSILON; if (!between(0, fT[0][index], 1)) { fUsed = 0; return false; } return true; } void upDepth() { SkASSERT(++fDepth < 16); } void alignQuadPts(const SkPoint a[3], const SkPoint b[3]); int cleanUpCoincidence(); int closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, double* dist) const; void cubicInsert(double one, double two, const SkDPoint& pt, const SkDCubic& c1, const SkDCubic& c2); void flip(); int horizontal(const SkDLine&, double left, double right, double y, bool flipped); int horizontal(const SkDQuad&, double left, double right, double y, bool flipped); int horizontal(const SkDQuad&, double left, double right, double y, double tRange[2]); int horizontal(const SkDCubic&, double y, double tRange[3]); int horizontal(const SkDConic&, double left, double right, double y, bool flipped); int horizontal(const SkDCubic&, double left, double right, double y, bool flipped); int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]); static double HorizontalIntercept(const SkDLine& line, double y); static int HorizontalIntercept(const SkDQuad& quad, SkScalar y, double* roots); static int HorizontalIntercept(const SkDConic& conic, SkScalar y, double* roots); // FIXME : does not respect swap int insert(double one, double two, const SkDPoint& pt); void insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2); // start if index == 0 : end if index == 1 int insertCoincident(double one, double two, const SkDPoint& pt); int intersect(const SkDLine&, const SkDLine&); int intersect(const SkDQuad&, const SkDLine&); int intersect(const SkDQuad&, const SkDQuad&); int intersect(const SkDConic&, const SkDLine&); int intersect(const SkDConic&, const SkDQuad&); int intersect(const SkDConic&, const SkDConic&); int intersect(const SkDCubic&, const SkDLine&); int intersect(const SkDCubic&, const SkDQuad&); int intersect(const SkDCubic&, const SkDConic&); int intersect(const SkDCubic&, const SkDCubic&); int intersectRay(const SkDLine&, const SkDLine&); int intersectRay(const SkDQuad&, const SkDLine&); int intersectRay(const SkDConic&, const SkDLine&); int intersectRay(const SkDCubic&, const SkDLine&); int intersectRay(const SkTCurve& tCurve, const SkDLine& line) { return tCurve.intersectRay(this, line); } void merge(const SkIntersections& , int , const SkIntersections& , int ); int mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const; void removeOne(int index); void setCoincident(int index); int vertical(const SkDLine&, double top, double bottom, double x, bool flipped); int vertical(const SkDQuad&, double top, double bottom, double x, bool flipped); int vertical(const SkDConic&, double top, double bottom, double x, bool flipped); int vertical(const SkDCubic&, double top, double bottom, double x, bool flipped); static double VerticalIntercept(const SkDLine& line, double x); static int VerticalIntercept(const SkDQuad& quad, SkScalar x, double* roots); static int VerticalIntercept(const SkDConic& conic, SkScalar x, double* roots); int depth() const { #ifdef SK_DEBUG return fDepth; #else return 0; #endif } enum DebugLoop { kIterations_DebugLoop, kCoinCheck_DebugLoop, kComputePerp_DebugLoop, }; void debugBumpLoopCount(DebugLoop ); int debugCoincidentUsed() const; int debugLoopCount(DebugLoop ) const; void debugResetLoopCount(); void dump() const; // implemented for testing only private: bool cubicCheckCoincidence(const SkDCubic& c1, const SkDCubic& c2); bool cubicExactEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2); void cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& ); void cleanUpParallelLines(bool parallel); void computePoints(const SkDLine& line, int used); SkDPoint fPt[13]; // FIXME: since scans store points as SkPoint, this should also SkDPoint fPt2[2]; // used by nearly same to store alternate intersection point double fT[2][13]; uint16_t fIsCoincident[2]; // bit set for each curve's coincident T bool fNearlySame[2]; // true if end points nearly match unsigned char fUsed; unsigned char fMax; bool fAllowNear; bool fSwap; #ifdef SK_DEBUG SkOpGlobalState* fDebugGlobalState; int fDepth; #endif #if DEBUG_T_SECT_LOOP_COUNT int fDebugLoopCount[3]; #endif }; struct SkPathOpsBounds; struct SkOpCurve { SkPoint fPts[4]; SkScalar fWeight; SkDEBUGCODE(SkPath::Verb fVerb); const SkPoint& operator[](int n) const { SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); return fPts[n]; } void dump() const; void set(const SkDQuad& quad) { for (int index = 0; index < SkDQuad::kPointCount; ++index) { fPts[index] = quad[index].asSkPoint(); } SkDEBUGCODE(fWeight = 1); SkDEBUGCODE(fVerb = SkPath::kQuad_Verb); } void set(const SkDCubic& cubic) { for (int index = 0; index < SkDCubic::kPointCount; ++index) { fPts[index] = cubic[index].asSkPoint(); } SkDEBUGCODE(fWeight = 1); SkDEBUGCODE(fVerb = SkPath::kCubic_Verb); } }; struct SkDCurve { union { SkDLine fLine; SkDQuad fQuad; SkDConic fConic; SkDCubic fCubic; }; SkDEBUGCODE(SkPath::Verb fVerb); const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); return fCubic[n]; } SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); return fCubic[n]; } SkDPoint conicTop(const SkPoint curve[3], SkScalar curveWeight, double s, double e, double* topT); SkDPoint cubicTop(const SkPoint curve[4], SkScalar , double s, double e, double* topT); void dump() const; void dumpID(int ) const; SkDPoint lineTop(const SkPoint[2], SkScalar , double , double , double* topT); double nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const; SkDPoint quadTop(const SkPoint curve[3], SkScalar , double s, double e, double* topT); void setConicBounds(const SkPoint curve[3], SkScalar curveWeight, double s, double e, SkPathOpsBounds* ); void setCubicBounds(const SkPoint curve[4], SkScalar , double s, double e, SkPathOpsBounds* ); void setQuadBounds(const SkPoint curve[3], SkScalar , double s, double e, SkPathOpsBounds*); }; class SkDCurveSweep { public: bool isCurve() const { return fIsCurve; } bool isOrdered() const { return fOrdered; } void setCurveHullSweep(SkPath::Verb verb); SkDCurve fCurve; SkDVector fSweep[2]; private: bool fIsCurve; bool fOrdered; // cleared when a cubic's control point isn't between the sweep vectors }; extern SkDPoint (SkDCurve::* const Top[])(const SkPoint curve[], SkScalar cWeight, double tStart, double tEnd, double* topT); static SkDPoint dline_xy_at_t(const SkPoint a[2], SkScalar , double t) { SkDLine line; line.set(a); return line.ptAtT(t); } static SkDPoint dquad_xy_at_t(const SkPoint a[3], SkScalar , double t) { SkDQuad quad; quad.set(a); return quad.ptAtT(t); } static SkDPoint dconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { SkDConic conic; conic.set(a, weight); return conic.ptAtT(t); } static SkDPoint dcubic_xy_at_t(const SkPoint a[4], SkScalar , double t) { SkDCubic cubic; cubic.set(a); return cubic.ptAtT(t); } static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], SkScalar , double ) = { nullptr, dline_xy_at_t, dquad_xy_at_t, dconic_xy_at_t, dcubic_xy_at_t }; static SkDPoint ddline_xy_at_t(const SkDCurve& c, double t) { return c.fLine.ptAtT(t); } static SkDPoint ddquad_xy_at_t(const SkDCurve& c, double t) { return c.fQuad.ptAtT(t); } static SkDPoint ddconic_xy_at_t(const SkDCurve& c, double t) { return c.fConic.ptAtT(t); } static SkDPoint ddcubic_xy_at_t(const SkDCurve& c, double t) { return c.fCubic.ptAtT(t); } static SkDPoint (* const CurveDDPointAtT[])(const SkDCurve& , double ) = { nullptr, ddline_xy_at_t, ddquad_xy_at_t, ddconic_xy_at_t, ddcubic_xy_at_t }; static SkPoint fline_xy_at_t(const SkPoint a[2], SkScalar weight, double t) { return dline_xy_at_t(a, weight, t).asSkPoint(); } static SkPoint fquad_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { return dquad_xy_at_t(a, weight, t).asSkPoint(); } static SkPoint fconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { return dconic_xy_at_t(a, weight, t).asSkPoint(); } static SkPoint fcubic_xy_at_t(const SkPoint a[4], SkScalar weight, double t) { return dcubic_xy_at_t(a, weight, t).asSkPoint(); } static SkPoint (* const CurvePointAtT[])(const SkPoint[], SkScalar , double ) = { nullptr, fline_xy_at_t, fquad_xy_at_t, fconic_xy_at_t, fcubic_xy_at_t }; static SkDVector dline_dxdy_at_t(const SkPoint a[2], SkScalar , double ) { SkDLine line; line.set(a); return line[1] - line[0]; } static SkDVector dquad_dxdy_at_t(const SkPoint a[3], SkScalar , double t) { SkDQuad quad; quad.set(a); return quad.dxdyAtT(t); } static SkDVector dconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { SkDConic conic; conic.set(a, weight); return conic.dxdyAtT(t); } static SkDVector dcubic_dxdy_at_t(const SkPoint a[4], SkScalar , double t) { SkDCubic cubic; cubic.set(a); return cubic.dxdyAtT(t); } static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], SkScalar , double ) = { nullptr, dline_dxdy_at_t, dquad_dxdy_at_t, dconic_dxdy_at_t, dcubic_dxdy_at_t }; static SkDVector ddline_dxdy_at_t(const SkDCurve& c, double ) { return c.fLine.fPts[1] - c.fLine.fPts[0]; } static SkDVector ddquad_dxdy_at_t(const SkDCurve& c, double t) { return c.fQuad.dxdyAtT(t); } static SkDVector ddconic_dxdy_at_t(const SkDCurve& c, double t) { return c.fConic.dxdyAtT(t); } static SkDVector ddcubic_dxdy_at_t(const SkDCurve& c, double t) { return c.fCubic.dxdyAtT(t); } static SkDVector (* const CurveDDSlopeAtT[])(const SkDCurve& , double ) = { nullptr, ddline_dxdy_at_t, ddquad_dxdy_at_t, ddconic_dxdy_at_t, ddcubic_dxdy_at_t }; static SkVector fline_dxdy_at_t(const SkPoint a[2], SkScalar , double ) { return a[1] - a[0]; } static SkVector fquad_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { return dquad_dxdy_at_t(a, weight, t).asSkVector(); } static SkVector fconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { return dconic_dxdy_at_t(a, weight, t).asSkVector(); } static SkVector fcubic_dxdy_at_t(const SkPoint a[4], SkScalar weight, double t) { return dcubic_dxdy_at_t(a, weight, t).asSkVector(); } static SkVector (* const CurveSlopeAtT[])(const SkPoint[], SkScalar , double ) = { nullptr, fline_dxdy_at_t, fquad_dxdy_at_t, fconic_dxdy_at_t, fcubic_dxdy_at_t }; static bool line_is_vertical(const SkPoint a[2], SkScalar , double startT, double endT) { SkDLine line; line.set(a); SkDPoint dst[2] = { line.ptAtT(startT), line.ptAtT(endT) }; return AlmostEqualUlps(dst[0].fX, dst[1].fX); } static bool quad_is_vertical(const SkPoint a[3], SkScalar , double startT, double endT) { SkDQuad quad; quad.set(a); SkDQuad dst = quad.subDivide(startT, endT); return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX); } static bool conic_is_vertical(const SkPoint a[3], SkScalar weight, double startT, double endT) { SkDConic conic; conic.set(a, weight); SkDConic dst = conic.subDivide(startT, endT); return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX); } static bool cubic_is_vertical(const SkPoint a[4], SkScalar , double startT, double endT) { SkDCubic cubic; cubic.set(a); SkDCubic dst = cubic.subDivide(startT, endT); return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX) && AlmostEqualUlps(dst[2].fX, dst[3].fX); } static bool (* const CurveIsVertical[])(const SkPoint[], SkScalar , double , double) = { nullptr, line_is_vertical, quad_is_vertical, conic_is_vertical, cubic_is_vertical }; static void line_intersect_ray(const SkPoint a[2], SkScalar , const SkDLine& ray, SkIntersections* i) { SkDLine line; line.set(a); i->intersectRay(line, ray); } static void quad_intersect_ray(const SkPoint a[3], SkScalar , const SkDLine& ray, SkIntersections* i) { SkDQuad quad; quad.set(a); i->intersectRay(quad, ray); } static void conic_intersect_ray(const SkPoint a[3], SkScalar weight, const SkDLine& ray, SkIntersections* i) { SkDConic conic; conic.set(a, weight); i->intersectRay(conic, ray); } static void cubic_intersect_ray(const SkPoint a[4], SkScalar , const SkDLine& ray, SkIntersections* i) { SkDCubic cubic; cubic.set(a); i->intersectRay(cubic, ray); } static void (* const CurveIntersectRay[])(const SkPoint[] , SkScalar , const SkDLine& , SkIntersections* ) = { nullptr, line_intersect_ray, quad_intersect_ray, conic_intersect_ray, cubic_intersect_ray }; static void dline_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { i->intersectRay(c.fLine, ray); } static void dquad_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { i->intersectRay(c.fQuad, ray); } static void dconic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { i->intersectRay(c.fConic, ray); } static void dcubic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { i->intersectRay(c.fCubic, ray); } static void (* const CurveDIntersectRay[])(const SkDCurve& , const SkDLine& , SkIntersections* ) = { nullptr, dline_intersect_ray, dquad_intersect_ray, dconic_intersect_ray, dcubic_intersect_ray }; static int line_intercept_h(const SkPoint a[2], SkScalar , SkScalar y, double* roots) { if (a[0].fY == a[1].fY) { return false; } SkDLine line; roots[0] = SkIntersections::HorizontalIntercept(line.set(a), y); return between(0, roots[0], 1); } static int line_intercept_v(const SkPoint a[2], SkScalar , SkScalar x, double* roots) { if (a[0].fX == a[1].fX) { return false; } SkDLine line; roots[0] = SkIntersections::VerticalIntercept(line.set(a), x); return between(0, roots[0], 1); } static int quad_intercept_h(const SkPoint a[2], SkScalar , SkScalar y, double* roots) { SkDQuad quad; return SkIntersections::HorizontalIntercept(quad.set(a), y, roots); } static int quad_intercept_v(const SkPoint a[2], SkScalar , SkScalar x, double* roots) { SkDQuad quad; return SkIntersections::VerticalIntercept(quad.set(a), x, roots); } static int conic_intercept_h(const SkPoint a[2], SkScalar w, SkScalar y, double* roots) { SkDConic conic; return SkIntersections::HorizontalIntercept(conic.set(a, w), y, roots); } static int conic_intercept_v(const SkPoint a[2], SkScalar w, SkScalar x, double* roots) { SkDConic conic; return SkIntersections::VerticalIntercept(conic.set(a, w), x, roots); } static int cubic_intercept_h(const SkPoint a[3], SkScalar , SkScalar y, double* roots) { SkDCubic cubic; return cubic.set(a).horizontalIntersect(y, roots); } static int cubic_intercept_v(const SkPoint a[3], SkScalar , SkScalar x, double* roots) { SkDCubic cubic; return cubic.set(a).verticalIntersect(x, roots); } static int (* const CurveIntercept[])(const SkPoint[] , SkScalar , SkScalar , double* ) = { nullptr, nullptr, line_intercept_h, line_intercept_v, quad_intercept_h, quad_intercept_v, conic_intercept_h, conic_intercept_v, cubic_intercept_h, cubic_intercept_v, }; #if DEBUG_ANGLE #endif class SkOpCoincidence; class SkOpContour; class SkOpPtT; class SkOpSegment; class SkOpSpan; class SkOpSpanBase; struct SkDPoint; struct SkDVector; class SkOpAngle { public: enum IncludeType { kUnaryWinding, kUnaryXor, kBinarySingle, kBinaryOpp, }; const SkOpAngle* debugAngle(int id) const; const SkOpCoincidence* debugCoincidence() const; SkOpContour* debugContour(int id) const; int debugID() const { return SkDEBUGRELEASE(fID, -1); } #if DEBUG_SORT void debugLoop() const; #endif #if DEBUG_ANGLE bool debugCheckCoincidence() const { return fCheckCoincidence; } void debugCheckNearCoincidence() const; SkString debugPart() const; #endif const SkOpPtT* debugPtT(int id) const; const SkOpSegment* debugSegment(int id) const; int debugSign() const; const SkOpSpanBase* debugSpan(int id) const; void debugValidate() const; void debugValidateNext() const; // in debug builds, verify that angle loop is uncorrupted double distEndRatio(double dist) const; // available to testing only void dump() const; void dumpCurves() const; void dumpLoop() const; void dumpOne(bool functionHeader) const; void dumpTo(const SkOpSegment* fromSeg, const SkOpAngle* ) const; void dumpTest() const; SkOpSpanBase* end() const { return fEnd; } bool insert(SkOpAngle* ); SkOpSpanBase* lastMarked() const; bool loopContains(const SkOpAngle* ) const; int loopCount() const; SkOpAngle* next() const { return fNext; } SkOpAngle* previous() const; SkOpSegment* segment() const; void set(SkOpSpanBase* start, SkOpSpanBase* end); void setLastMarked(SkOpSpanBase* marked) { fLastMarked = marked; } SkOpSpanBase* start() const { return fStart; } SkOpSpan* starter(); bool tangentsAmbiguous() const { return fTangentsAmbiguous; } bool unorderable() const { return fUnorderable; } private: bool after(SkOpAngle* test); void alignmentSameSide(const SkOpAngle* test, int* order) const; bool checkCrossesZero() const; bool checkParallel(SkOpAngle* ); bool computeSector(); int convexHullOverlaps(const SkOpAngle* ); bool endToSide(const SkOpAngle* rh, bool* inside) const; bool endsIntersect(SkOpAngle* ); int findSector(SkPath::Verb verb, double x, double y) const; SkOpGlobalState* globalState() const; int lineOnOneSide(const SkDPoint& origin, const SkDVector& line, const SkOpAngle* test, bool useOriginal) const; int lineOnOneSide(const SkOpAngle* test, bool useOriginal); int linesOnOriginalSide(const SkOpAngle* test); bool merge(SkOpAngle* ); double midT() const; bool midToSide(const SkOpAngle* rh, bool* inside) const; bool oppositePlanes(const SkOpAngle* rh) const; int orderable(SkOpAngle* rh); // false == this < rh ; true == this > rh; -1 == unorderable void setSector(); void setSpans(); bool tangentsDiverge(const SkOpAngle* rh, double s0xt0); SkDCurve fOriginalCurvePart; // the curve from start to end SkDCurveSweep fPart; // the curve from start to end offset as needed double fSide; SkLineParameters fTangentHalf; // used only to sort a pair of lines or line-like sections SkOpAngle* fNext; SkOpSpanBase* fLastMarked; SkOpSpanBase* fStart; SkOpSpanBase* fEnd; SkOpSpanBase* fComputedEnd; int fSectorMask; int8_t fSectorStart; // in 32nds of a circle int8_t fSectorEnd; bool fUnorderable; bool fComputeSector; bool fComputedSector; bool fCheckCoincidence; bool fTangentsAmbiguous; SkDEBUGCODE(int fID); friend class PathOpsAngleTester; }; class SkOpAngle; class SkOpCoincidence; class SkOpContour; class SkOpSegment; class SkOpSpan; class SkOpSpanBase; // subset of op span used by terminal span (when t is equal to one) class SkOpPtT { public: enum { kIsAlias = 1, kIsDuplicate = 1 }; const SkOpPtT* active() const; // please keep in sync with debugAddOpp() void addOpp(SkOpPtT* opp, SkOpPtT* oppPrev) { SkOpPtT* oldNext = this->fNext; SkASSERT(this != opp); this->fNext = opp; SkASSERT(oppPrev != oldNext); oppPrev->fNext = oldNext; } bool alias() const; bool coincident() const { return fCoincident; } bool contains(const SkOpPtT* ) const; bool contains(const SkOpSegment*, const SkPoint& ) const; bool contains(const SkOpSegment*, double t) const; const SkOpPtT* contains(const SkOpSegment* ) const; SkOpContour* contour() const; int debugID() const { return SkDEBUGRELEASE(fID, -1); } void debugAddOpp(const SkOpPtT* opp, const SkOpPtT* oppPrev) const; const SkOpAngle* debugAngle(int id) const; const SkOpCoincidence* debugCoincidence() const; bool debugContains(const SkOpPtT* ) const; const SkOpPtT* debugContains(const SkOpSegment* check) const; SkOpContour* debugContour(int id) const; const SkOpPtT* debugEnder(const SkOpPtT* end) const; int debugLoopLimit(bool report) const; bool debugMatchID(int id) const; const SkOpPtT* debugOppPrev(const SkOpPtT* opp) const; const SkOpPtT* debugPtT(int id) const; void debugResetCoinT() const; const SkOpSegment* debugSegment(int id) const; void debugSetCoinT(int ) const; const SkOpSpanBase* debugSpan(int id) const; void debugValidate() const; bool deleted() const { return fDeleted; } bool duplicate() const { return fDuplicatePt; } void dump() const; // available to testing only void dumpAll() const; void dumpBase() const; const SkOpPtT* find(const SkOpSegment* ) const; SkOpGlobalState* globalState() const; void init(SkOpSpanBase* , double t, const SkPoint& , bool dup); void insert(SkOpPtT* span) { SkASSERT(span != this); span->fNext = fNext; fNext = span; } const SkOpPtT* next() const { return fNext; } SkOpPtT* next() { return fNext; } bool onEnd() const; // returns nullptr if this is already in the opp ptT loop SkOpPtT* oppPrev(const SkOpPtT* opp) const { // find the fOpp ptr to opp SkOpPtT* oppPrev = opp->fNext; if (oppPrev == this) { return nullptr; } while (oppPrev->fNext != opp) { oppPrev = oppPrev->fNext; if (oppPrev == this) { return nullptr; } } return oppPrev; } static bool Overlaps(const SkOpPtT* s1, const SkOpPtT* e1, const SkOpPtT* s2, const SkOpPtT* e2, const SkOpPtT** sOut, const SkOpPtT** eOut) { const SkOpPtT* start1 = s1->fT < e1->fT ? s1 : e1; const SkOpPtT* start2 = s2->fT < e2->fT ? s2 : e2; *sOut = between(s1->fT, start2->fT, e1->fT) ? start2 : between(s2->fT, start1->fT, e2->fT) ? start1 : nullptr; const SkOpPtT* end1 = s1->fT < e1->fT ? e1 : s1; const SkOpPtT* end2 = s2->fT < e2->fT ? e2 : s2; *eOut = between(s1->fT, end2->fT, e1->fT) ? end2 : between(s2->fT, end1->fT, e2->fT) ? end1 : nullptr; if (*sOut == *eOut) { SkOPOBJASSERT(s1, start1->fT >= end2->fT || start2->fT >= end1->fT); return false; } SkASSERT(!*sOut || *sOut != *eOut); return *sOut && *eOut; } bool ptAlreadySeen(const SkOpPtT* head) const; SkOpPtT* prev(); const SkOpSegment* segment() const; SkOpSegment* segment(); void setCoincident() const { SkOPASSERT(!fDeleted); fCoincident = true; } void setDeleted(); void setSpan(const SkOpSpanBase* span) { fSpan = const_cast(span); } const SkOpSpanBase* span() const { return fSpan; } SkOpSpanBase* span() { return fSpan; } const SkOpPtT* starter(const SkOpPtT* end) const { return fT < end->fT ? this : end; } double fT; SkPoint fPt; // cache of point value at this t protected: SkOpSpanBase* fSpan; // contains winding data SkOpPtT* fNext; // intersection on opposite curve or alias on this curve bool fDeleted; // set if removed from span list bool fDuplicatePt; // set if identical pt is somewhere in the next loop // below mutable since referrer is otherwise always const mutable bool fCoincident; // set if at some point a coincident span pointed here SkDEBUGCODE(int fID); }; class SkOpSpanBase { public: enum class Collapsed { kNo, kYes, kError, }; bool addOpp(SkOpSpanBase* opp); void bumpSpanAdds() { ++fSpanAdds; } bool chased() const { return fChased; } void checkForCollapsedCoincidence(); const SkOpSpanBase* coinEnd() const { return fCoinEnd; } Collapsed collapsed(double s, double e) const; bool contains(const SkOpSpanBase* ) const; const SkOpPtT* contains(const SkOpSegment* ) const; bool containsCoinEnd(const SkOpSpanBase* coin) const { SkASSERT(this != coin); const SkOpSpanBase* next = this; while ((next = next->fCoinEnd) != this) { if (next == coin) { return true; } } return false; } bool containsCoinEnd(const SkOpSegment* ) const; SkOpContour* contour() const; int debugBumpCount() { return SkDEBUGRELEASE(++fCount, -1); } int debugID() const { return SkDEBUGRELEASE(fID, -1); } #if DEBUG_COIN void debugAddOpp(SkPathOpsDebug::GlitchLog* , const SkOpSpanBase* opp) const; #endif bool debugAlignedEnd(double t, const SkPoint& pt) const; bool debugAlignedInner() const; const SkOpAngle* debugAngle(int id) const; #if DEBUG_COIN void debugCheckForCollapsedCoincidence(SkPathOpsDebug::GlitchLog* ) const; #endif const SkOpCoincidence* debugCoincidence() const; bool debugCoinEndLoopCheck() const; SkOpContour* debugContour(int id) const; #ifdef SK_DEBUG bool debugDeleted() const { return fDebugDeleted; } #endif #if DEBUG_COIN void debugInsertCoinEnd(SkPathOpsDebug::GlitchLog* , const SkOpSpanBase* ) const; void debugMergeMatches(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp) const; #endif const SkOpPtT* debugPtT(int id) const; void debugResetCoinT() const; const SkOpSegment* debugSegment(int id) const; void debugSetCoinT(int ) const; #ifdef SK_DEBUG void debugSetDeleted() { fDebugDeleted = true; } #endif const SkOpSpanBase* debugSpan(int id) const; const SkOpSpan* debugStarter(SkOpSpanBase const** endPtr) const; SkOpGlobalState* globalState() const; void debugValidate() const; bool deleted() const { return fPtT.deleted(); } void dump() const; // available to testing only void dumpCoin() const; void dumpAll() const; void dumpBase() const; void dumpHead() const; bool final() const { return fPtT.fT == 1; } SkOpAngle* fromAngle() const { return fFromAngle; } void initBase(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); // Please keep this in sync with debugInsertCoinEnd() void insertCoinEnd(SkOpSpanBase* coin) { if (containsCoinEnd(coin)) { SkASSERT(coin->containsCoinEnd(this)); return; } debugValidate(); SkASSERT(this != coin); SkOpSpanBase* coinNext = coin->fCoinEnd; coin->fCoinEnd = this->fCoinEnd; this->fCoinEnd = coinNext; debugValidate(); } void merge(SkOpSpan* span); bool mergeMatches(SkOpSpanBase* opp); const SkOpSpan* prev() const { return fPrev; } SkOpSpan* prev() { return fPrev; } const SkPoint& pt() const { return fPtT.fPt; } const SkOpPtT* ptT() const { return &fPtT; } SkOpPtT* ptT() { return &fPtT; } SkOpSegment* segment() const { return fSegment; } void setAligned() { fAligned = true; } void setChased(bool chased) { fChased = chased; } void setFromAngle(SkOpAngle* angle) { fFromAngle = angle; } void setPrev(SkOpSpan* prev) { fPrev = prev; } bool simple() const { fPtT.debugValidate(); return fPtT.next()->next() == &fPtT; } int spanAddsCount() const { return fSpanAdds; } const SkOpSpan* starter(const SkOpSpanBase* end) const { const SkOpSpanBase* result = t() < end->t() ? this : end; return result->upCast(); } SkOpSpan* starter(SkOpSpanBase* end) { SkASSERT(this->segment() == end->segment()); SkOpSpanBase* result = t() < end->t() ? this : end; return result->upCast(); } SkOpSpan* starter(SkOpSpanBase** endPtr) { SkOpSpanBase* end = *endPtr; SkASSERT(this->segment() == end->segment()); SkOpSpanBase* result; if (t() < end->t()) { result = this; } else { result = end; *endPtr = this; } return result->upCast(); } int step(const SkOpSpanBase* end) const { return t() < end->t() ? 1 : -1; } double t() const { return fPtT.fT; } void unaligned() { fAligned = false; } SkOpSpan* upCast() { SkASSERT(!final()); return (SkOpSpan*) this; } const SkOpSpan* upCast() const { SkOPASSERT(!final()); return (const SkOpSpan*) this; } SkOpSpan* upCastable() { return final() ? nullptr : upCast(); } const SkOpSpan* upCastable() const { return final() ? nullptr : upCast(); } private: void alignInner(); protected: // no direct access to internals to avoid treating a span base as a span SkOpPtT fPtT; // list of points and t values associated with the start of this span SkOpSegment* fSegment; // segment that contains this span SkOpSpanBase* fCoinEnd; // linked list of coincident spans that end here (may point to itself) SkOpAngle* fFromAngle; // points to next angle from span start to end SkOpSpan* fPrev; // previous intersection point int fSpanAdds; // number of times intersections have been added to span bool fAligned; bool fChased; // set after span has been added to chase array SkDEBUGCODE(int fCount); // number of pt/t pairs added SkDEBUGCODE(int fID); SkDEBUGCODE(bool fDebugDeleted); // set when span was merged with another span }; class SkOpSpan : public SkOpSpanBase { public: bool alreadyAdded() const { if (fAlreadyAdded) { return true; } return false; } bool clearCoincident() { SkASSERT(!final()); if (fCoincident == this) { return false; } fCoincident = this; return true; } int computeWindSum(); bool containsCoincidence(const SkOpSegment* ) const; bool containsCoincidence(const SkOpSpan* coin) const { SkASSERT(this != coin); const SkOpSpan* next = this; while ((next = next->fCoincident) != this) { if (next == coin) { return true; } } return false; } bool debugCoinLoopCheck() const; #if DEBUG_COIN void debugInsertCoincidence(SkPathOpsDebug::GlitchLog* , const SkOpSpan* ) const; void debugInsertCoincidence(SkPathOpsDebug::GlitchLog* , const SkOpSegment* , bool flipped, bool ordered) const; #endif void dumpCoin() const; bool dumpSpan() const; bool done() const { SkASSERT(!final()); return fDone; } void init(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); bool insertCoincidence(const SkOpSegment* , bool flipped, bool ordered); // Please keep this in sync with debugInsertCoincidence() void insertCoincidence(SkOpSpan* coin) { if (containsCoincidence(coin)) { SkASSERT(coin->containsCoincidence(this)); return; } debugValidate(); SkASSERT(this != coin); SkOpSpan* coinNext = coin->fCoincident; coin->fCoincident = this->fCoincident; this->fCoincident = coinNext; debugValidate(); } bool isCanceled() const { SkASSERT(!final()); return fWindValue == 0 && fOppValue == 0; } bool isCoincident() const { SkASSERT(!final()); return fCoincident != this; } void markAdded() { fAlreadyAdded = true; } SkOpSpanBase* next() const { SkASSERT(!final()); return fNext; } int oppSum() const { SkASSERT(!final()); return fOppSum; } int oppValue() const { SkASSERT(!final()); return fOppValue; } void release(const SkOpPtT* ); SkOpPtT* setCoinStart(SkOpSpan* oldCoinStart, SkOpSegment* oppSegment); void setDone(bool done) { SkASSERT(!final()); fDone = done; } void setNext(SkOpSpanBase* nextT) { SkASSERT(!final()); fNext = nextT; } void setOppSum(int oppSum); void setOppValue(int oppValue) { SkASSERT(!final()); SkASSERT(fOppSum == SK_MinS32); SkOPASSERT(!oppValue || !fDone); fOppValue = oppValue; } void setToAngle(SkOpAngle* angle) { SkASSERT(!final()); fToAngle = angle; } void setWindSum(int windSum); void setWindValue(int windValue) { SkASSERT(!final()); SkASSERT(windValue >= 0); SkASSERT(fWindSum == SK_MinS32); SkOPASSERT(!windValue || !fDone); fWindValue = windValue; } bool sortableTop(SkOpContour* ); SkOpAngle* toAngle() const { SkASSERT(!final()); return fToAngle; } int windSum() const { SkASSERT(!final()); return fWindSum; } int windValue() const { SkOPASSERT(!final()); return fWindValue; } private: // no direct access to internals to avoid treating a span base as a span SkOpSpan* fCoincident; // linked list of spans coincident with this one (may point to itself) SkOpAngle* fToAngle; // points to next angle from span start to end SkOpSpanBase* fNext; // next intersection point int fWindSum; // accumulated from contours surrounding this one. int fOppSum; // for binary operators: the opposite winding sum int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here int fTopTTry; // specifies direction and t value to try next bool fDone; // if set, this span to next higher T has been processed bool fAlreadyAdded; }; class SkTCurve; struct SkDConic; struct SkDCubic; struct SkDQuad; struct SkDRect { double fLeft, fTop, fRight, fBottom; void add(const SkDPoint& pt) { fLeft = std::min(fLeft, pt.fX); fTop = std::min(fTop, pt.fY); fRight = std::max(fRight, pt.fX); fBottom = std::max(fBottom, pt.fY); } bool contains(const SkDPoint& pt) const { return approximately_between(fLeft, pt.fX, fRight) && approximately_between(fTop, pt.fY, fBottom); } void debugInit(); bool intersects(const SkDRect& r) const { SkASSERT(fLeft <= fRight); SkASSERT(fTop <= fBottom); SkASSERT(r.fLeft <= r.fRight); SkASSERT(r.fTop <= r.fBottom); return r.fLeft <= fRight && fLeft <= r.fRight && r.fTop <= fBottom && fTop <= r.fBottom; } void set(const SkDPoint& pt) { fLeft = fRight = pt.fX; fTop = fBottom = pt.fY; } double width() const { return fRight - fLeft; } double height() const { return fBottom - fTop; } void setBounds(const SkDConic& curve) { setBounds(curve, curve, 0, 1); } void setBounds(const SkDConic& curve, const SkDConic& sub, double tStart, double tEnd); void setBounds(const SkDCubic& curve) { setBounds(curve, curve, 0, 1); } void setBounds(const SkDCubic& curve, const SkDCubic& sub, double tStart, double tEnd); void setBounds(const SkDQuad& curve) { setBounds(curve, curve, 0, 1); } void setBounds(const SkDQuad& curve, const SkDQuad& sub, double tStart, double tEnd); void setBounds(const SkTCurve& curve); bool valid() const { return fLeft <= fRight && fTop <= fBottom; } }; // SkPathOpsBounds, unlike SkRect, does not consider a line to be empty. struct SkPathOpsBounds : public SkRect { static bool Intersects(const SkPathOpsBounds& a, const SkPathOpsBounds& b) { return AlmostLessOrEqualUlps(a.fLeft, b.fRight) && AlmostLessOrEqualUlps(b.fLeft, a.fRight) && AlmostLessOrEqualUlps(a.fTop, b.fBottom) && AlmostLessOrEqualUlps(b.fTop, a.fBottom); } // Note that add(), unlike SkRect::join() or SkRect::growToInclude() // does not treat the bounds of horizontal and vertical lines as // empty rectangles. void add(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) { if (left < fLeft) fLeft = left; if (top < fTop) fTop = top; if (right > fRight) fRight = right; if (bottom > fBottom) fBottom = bottom; } void add(const SkPathOpsBounds& toAdd) { add(toAdd.fLeft, toAdd.fTop, toAdd.fRight, toAdd.fBottom); } void add(const SkPoint& pt) { if (pt.fX < fLeft) fLeft = pt.fX; if (pt.fY < fTop) fTop = pt.fY; if (pt.fX > fRight) fRight = pt.fX; if (pt.fY > fBottom) fBottom = pt.fY; } void add(const SkDPoint& pt) { if (pt.fX < fLeft) fLeft = SkDoubleToScalar(pt.fX); if (pt.fY < fTop) fTop = SkDoubleToScalar(pt.fY); if (pt.fX > fRight) fRight = SkDoubleToScalar(pt.fX); if (pt.fY > fBottom) fBottom = SkDoubleToScalar(pt.fY); } bool almostContains(const SkPoint& pt) const { return AlmostLessOrEqualUlps(fLeft, pt.fX) && AlmostLessOrEqualUlps(pt.fX, fRight) && AlmostLessOrEqualUlps(fTop, pt.fY) && AlmostLessOrEqualUlps(pt.fY, fBottom); } bool contains(const SkPoint& pt) const { return fLeft <= pt.fX && fTop <= pt.fY && fRight >= pt.fX && fBottom >= pt.fY; } using INHERITED = SkRect; }; enum class SkOpRayDir; class SkOpCoincidence; class SkOpContour; class SkPathWriter; struct SkOpRayHit; template class SkTDArray; class SkOpSegment { public: bool operator<(const SkOpSegment& rh) const { return fBounds.fTop < rh.fBounds.fTop; } SkOpAngle* activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, bool* done); SkOpAngle* activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, bool* done); SkOpAngle* activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, bool* done); bool activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, SkPathOp op); bool activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, SkPathOp op, int* sumMiWinding, int* sumSuWinding); bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end); bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding); SkOpSegment* addConic(SkPoint pts[3], SkScalar weight, SkOpContour* parent) { init(pts, weight, parent, SkPath::kConic_Verb); SkDCurve curve; curve.fConic.set(pts, weight); curve.setConicBounds(pts, weight, 0, 1, &fBounds); return this; } SkOpSegment* addCubic(SkPoint pts[4], SkOpContour* parent) { init(pts, 1, parent, SkPath::kCubic_Verb); SkDCurve curve; curve.fCubic.set(pts); curve.setCubicBounds(pts, 1, 0, 1, &fBounds); return this; } bool addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPathWriter* path) const; SkOpAngle* addEndSpan() { SkOpAngle* angle = this->globalState()->allocator()->make(); angle->set(&fTail, fTail.prev()); fTail.setFromAngle(angle); return angle; } bool addExpanded(double newT, const SkOpSpanBase* test, bool* startOver); SkOpSegment* addLine(SkPoint pts[2], SkOpContour* parent) { SkASSERT(pts[0] != pts[1]); init(pts, 1, parent, SkPath::kLine_Verb); fBounds.setBounds(pts, 2); return this; } SkOpPtT* addMissing(double t, SkOpSegment* opp, bool* allExist); SkOpAngle* addStartSpan() { SkOpAngle* angle = this->globalState()->allocator()->make(); angle->set(&fHead, fHead.next()); fHead.setToAngle(angle); return angle; } SkOpSegment* addQuad(SkPoint pts[3], SkOpContour* parent) { init(pts, 1, parent, SkPath::kQuad_Verb); SkDCurve curve; curve.fQuad.set(pts); curve.setQuadBounds(pts, 1, 0, 1, &fBounds); return this; } SkOpPtT* addT(double t); SkOpPtT* addT(double t, const SkPoint& pt); const SkPathOpsBounds& bounds() const { return fBounds; } void bumpCount() { ++fCount; } void calcAngles(); SkOpSpanBase::Collapsed collapsed(double startT, double endT) const; static bool ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, SkOpAngle::IncludeType ); static bool ComputeOneSumReverse(SkOpAngle* baseAngle, SkOpAngle* nextAngle, SkOpAngle::IncludeType ); int computeSum(SkOpSpanBase* start, SkOpSpanBase* end, SkOpAngle::IncludeType includeType); void clearAll(); void clearOne(SkOpSpan* span); static void ClearVisited(SkOpSpanBase* span); bool contains(double t) const; SkOpContour* contour() const { return fContour; } int count() const { return fCount; } void debugAddAngle(double startT, double endT); #if DEBUG_COIN const SkOpPtT* debugAddT(double t, SkPathOpsDebug::GlitchLog* ) const; #endif const SkOpAngle* debugAngle(int id) const; #if DEBUG_ANGLE void debugCheckAngleCoin() const; #endif #if DEBUG_COIN void debugCheckHealth(SkPathOpsDebug::GlitchLog* ) const; void debugClearAll(SkPathOpsDebug::GlitchLog* glitches) const; void debugClearOne(const SkOpSpan* span, SkPathOpsDebug::GlitchLog* glitches) const; #endif const SkOpCoincidence* debugCoincidence() const; SkOpContour* debugContour(int id) const; int debugID() const { return SkDEBUGRELEASE(fID, -1); } SkOpAngle* debugLastAngle(); #if DEBUG_COIN void debugMissingCoincidence(SkPathOpsDebug::GlitchLog* glitches) const; void debugMoveMultiples(SkPathOpsDebug::GlitchLog* glitches) const; void debugMoveNearby(SkPathOpsDebug::GlitchLog* glitches) const; #endif const SkOpPtT* debugPtT(int id) const; void debugReset(); const SkOpSegment* debugSegment(int id) const; #if DEBUG_ACTIVE_SPANS void debugShowActiveSpans(SkString* str) const; #endif #if DEBUG_MARK_DONE void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding); void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, int oppWinding); #endif const SkOpSpanBase* debugSpan(int id) const; void debugValidate() const; #if DEBUG_COINCIDENCE_ORDER void debugResetCoinT() const; void debugSetCoinT(int, SkScalar ) const; #endif #if DEBUG_COIN static void DebugClearVisited(const SkOpSpanBase* span); bool debugVisited() const { if (!fDebugVisited) { fDebugVisited = true; return false; } return true; } #endif #if DEBUG_ANGLE double distSq(double t, const SkOpAngle* opp) const; #endif bool done() const { SkOPASSERT(fDoneCount <= fCount); return fDoneCount == fCount; } bool done(const SkOpAngle* angle) const { return angle->start()->starter(angle->end())->done(); } SkDPoint dPtAtT(double mid) const { return (*CurveDPointAtT[fVerb])(fPts, fWeight, mid); } SkDVector dSlopeAtT(double mid) const { return (*CurveDSlopeAtT[fVerb])(fPts, fWeight, mid); } void dump() const; void dumpAll() const; void dumpAngles() const; void dumpCoin() const; void dumpPts(const char* prefix = "seg") const; void dumpPtsInner(const char* prefix = "seg") const; const SkOpPtT* existing(double t, const SkOpSegment* opp) const; SkOpSegment* findNextOp(SkTDArray* chase, SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable, bool* simple, SkPathOp op, int xorMiMask, int xorSuMask); SkOpSegment* findNextWinding(SkTDArray* chase, SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable); SkOpSegment* findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable); SkOpSpan* findSortableTop(SkOpContour* ); SkOpGlobalState* globalState() const; const SkOpSpan* head() const { return &fHead; } SkOpSpan* head() { return &fHead; } void init(SkPoint pts[], SkScalar weight, SkOpContour* parent, SkPath::Verb verb); SkOpSpan* insert(SkOpSpan* prev) { SkOpGlobalState* globalState = this->globalState(); globalState->setAllocatedOpSpan(); SkOpSpan* result = globalState->allocator()->make(); SkOpSpanBase* next = prev->next(); result->setPrev(prev); prev->setNext(result); SkDEBUGCODE(result->ptT()->fT = 0); result->setNext(next); if (next) { next->setPrev(result); } return result; } bool isClose(double t, const SkOpSegment* opp) const; bool isHorizontal() const { return fBounds.fTop == fBounds.fBottom; } SkOpSegment* isSimple(SkOpSpanBase** end, int* step) const { return nextChase(end, step, nullptr, nullptr); } bool isVertical() const { return fBounds.fLeft == fBounds.fRight; } bool isVertical(SkOpSpanBase* start, SkOpSpanBase* end) const { return (*CurveIsVertical[fVerb])(fPts, fWeight, start->t(), end->t()); } bool isXor() const; void joinEnds(SkOpSegment* start) { fTail.ptT()->addOpp(start->fHead.ptT(), start->fHead.ptT()); } const SkPoint& lastPt() const { return fPts[SkPathOpsVerbToPoints(fVerb)]; } void markAllDone(); bool markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end, SkOpSpanBase** found); bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, SkOpSpanBase** lastPtr); bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, int oppWinding, SkOpSpanBase** lastPtr); bool markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle, SkOpSpanBase** result); bool markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, const SkOpAngle* angle, SkOpSpanBase** result); void markDone(SkOpSpan* ); bool markWinding(SkOpSpan* , int winding); bool markWinding(SkOpSpan* , int winding, int oppWinding); bool match(const SkOpPtT* span, const SkOpSegment* parent, double t, const SkPoint& pt) const; bool missingCoincidence(); bool moveMultiples(); bool moveNearby(); SkOpSegment* next() const { return fNext; } SkOpSegment* nextChase(SkOpSpanBase** , int* step, SkOpSpan** , SkOpSpanBase** last) const; bool operand() const; static int OppSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { int result = start->t() < end->t() ? -start->upCast()->oppValue() : end->upCast()->oppValue(); return result; } bool oppXor() const; const SkOpSegment* prev() const { return fPrev; } SkPoint ptAtT(double mid) const { return (*CurvePointAtT[fVerb])(fPts, fWeight, mid); } const SkPoint* pts() const { return fPts; } bool ptsDisjoint(const SkOpPtT& span, const SkOpPtT& test) const { SkASSERT(this == span.segment()); SkASSERT(this == test.segment()); return ptsDisjoint(span.fT, span.fPt, test.fT, test.fPt); } bool ptsDisjoint(const SkOpPtT& span, double t, const SkPoint& pt) const { SkASSERT(this == span.segment()); return ptsDisjoint(span.fT, span.fPt, t, pt); } bool ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const; void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkArenaAlloc*); void release(const SkOpSpan* ); #if DEBUG_COIN void resetDebugVisited() const { fDebugVisited = false; } #endif void resetVisited() { fVisited = false; } void setContour(SkOpContour* contour) { fContour = contour; } void setNext(SkOpSegment* next) { fNext = next; } void setPrev(SkOpSegment* prev) { fPrev = prev; } void setUpWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* maxWinding, int* sumWinding) { int deltaSum = SpanSign(start, end); *maxWinding = *sumWinding; if (*sumWinding == SK_MinS32) { return; } *sumWinding -= deltaSum; } void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* maxWinding, int* sumWinding); void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); bool sortAngles(); bool spansNearby(const SkOpSpanBase* ref, const SkOpSpanBase* check, bool* found) const; static int SpanSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { int result = start->t() < end->t() ? -start->upCast()->windValue() : end->upCast()->windValue(); return result; } SkOpAngle* spanToAngle(SkOpSpanBase* start, SkOpSpanBase* end) { SkASSERT(start != end); return start->t() < end->t() ? start->upCast()->toAngle() : start->fromAngle(); } bool subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, SkDCurve* result) const; const SkOpSpanBase* tail() const { return &fTail; } SkOpSpanBase* tail() { return &fTail; } bool testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, const SkOpSpanBase* prior, const SkOpSpanBase* spanBase, const SkOpSegment* opp) const; SkOpSpan* undoneSpan(); int updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const; int updateOppWinding(const SkOpAngle* angle) const; int updateOppWindingReverse(const SkOpAngle* angle) const; int updateWinding(SkOpSpanBase* start, SkOpSpanBase* end); int updateWinding(SkOpAngle* angle); int updateWindingReverse(const SkOpAngle* angle); static bool UseInnerWinding(int outerWinding, int innerWinding); SkPath::Verb verb() const { return fVerb; } // look for two different spans that point to the same opposite segment bool visited() { if (!fVisited) { fVisited = true; return false; } return true; } SkScalar weight() const { return fWeight; } SkOpSpan* windingSpanAtT(double tHit); int windSum(const SkOpAngle* angle) const; private: SkOpSpan fHead; // the head span always has its t set to zero SkOpSpanBase fTail; // the tail span always has its t set to one SkOpContour* fContour; SkOpSegment* fNext; // forward-only linked list used by contour to walk the segments const SkOpSegment* fPrev; SkPoint* fPts; // pointer into array of points owned by edge builder that may be tweaked SkPathOpsBounds fBounds; // tight bounds SkScalar fWeight; int fCount; // number of spans (one for a non-intersecting segment) int fDoneCount; // number of processed spans (zero initially) SkPath::Verb fVerb; bool fVisited; // used by missing coincidence check #if DEBUG_COIN mutable bool fDebugVisited; // used by debug missing coincidence check #endif #if DEBUG_COINCIDENCE_ORDER mutable int fDebugBaseIndex; mutable SkScalar fDebugBaseMin; // if > 0, the 1st t value in this seg vis-a-vis the ref seg mutable SkScalar fDebugBaseMax; mutable int fDebugLastIndex; mutable SkScalar fDebugLastMin; // if > 0, the last t -- next t val - base has same sign mutable SkScalar fDebugLastMax; #endif SkDEBUGCODE(int fID); }; class SkOpAngle; class SkOpCoincidence; class SkPathWriter; enum class SkOpRayDir; struct SkOpRayHit; class SkOpContour { public: SkOpContour() { reset(); } bool operator<(const SkOpContour& rh) const { return fBounds.fTop == rh.fBounds.fTop ? fBounds.fLeft < rh.fBounds.fLeft : fBounds.fTop < rh.fBounds.fTop; } void addConic(SkPoint pts[3], SkScalar weight) { appendSegment().addConic(pts, weight, this); } void addCubic(SkPoint pts[4]) { appendSegment().addCubic(pts, this); } SkOpSegment* addLine(SkPoint pts[2]) { SkASSERT(pts[0] != pts[1]); return appendSegment().addLine(pts, this); } void addQuad(SkPoint pts[3]) { appendSegment().addQuad(pts, this); } SkOpSegment& appendSegment() { SkOpSegment* result = fCount++ ? this->globalState()->allocator()->make() : &fHead; result->setPrev(fTail); if (fTail) { fTail->setNext(result); } fTail = result; return *result; } const SkPathOpsBounds& bounds() const { return fBounds; } void calcAngles() { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; do { segment->calcAngles(); } while ((segment = segment->next())); } void complete() { setBounds(); } int count() const { return fCount; } int debugID() const { return SkDEBUGRELEASE(fID, -1); } int debugIndent() const { return SkDEBUGRELEASE(fDebugIndent, 0); } const SkOpAngle* debugAngle(int id) const { return SkDEBUGRELEASE(this->globalState()->debugAngle(id), nullptr); } const SkOpCoincidence* debugCoincidence() const { return this->globalState()->coincidence(); } #if DEBUG_COIN void debugCheckHealth(SkPathOpsDebug::GlitchLog* ) const; #endif SkOpContour* debugContour(int id) const { return SkDEBUGRELEASE(this->globalState()->debugContour(id), nullptr); } #if DEBUG_COIN void debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const; void debugMoveMultiples(SkPathOpsDebug::GlitchLog* ) const; void debugMoveNearby(SkPathOpsDebug::GlitchLog* log) const; #endif const SkOpPtT* debugPtT(int id) const { return SkDEBUGRELEASE(this->globalState()->debugPtT(id), nullptr); } const SkOpSegment* debugSegment(int id) const { return SkDEBUGRELEASE(this->globalState()->debugSegment(id), nullptr); } #if DEBUG_ACTIVE_SPANS void debugShowActiveSpans(SkString* str) { SkOpSegment* segment = &fHead; do { segment->debugShowActiveSpans(str); } while ((segment = segment->next())); } #endif const SkOpSpanBase* debugSpan(int id) const { return SkDEBUGRELEASE(this->globalState()->debugSpan(id), nullptr); } SkOpGlobalState* globalState() const { return fState; } void debugValidate() const { #if DEBUG_VALIDATE const SkOpSegment* segment = &fHead; const SkOpSegment* prior = nullptr; do { segment->debugValidate(); SkASSERT(segment->prev() == prior); prior = segment; } while ((segment = segment->next())); SkASSERT(prior == fTail); #endif } bool done() const { return fDone; } void dump() const; void dumpAll() const; void dumpAngles() const; void dumpContours() const; void dumpContoursAll() const; void dumpContoursAngles() const; void dumpContoursPts() const; void dumpContoursPt(int segmentID) const; void dumpContoursSegment(int segmentID) const; void dumpContoursSpan(int segmentID) const; void dumpContoursSpans() const; void dumpPt(int ) const; void dumpPts(const char* prefix = "seg") const; void dumpPtsX(const char* prefix) const; void dumpSegment(int ) const; void dumpSegments(const char* prefix = "seg", SkPathOp op = (SkPathOp) -1) const; void dumpSpan(int ) const; void dumpSpans() const; const SkPoint& end() const { return fTail->pts()[SkPathOpsVerbToPoints(fTail->verb())]; } SkOpSpan* findSortableTop(SkOpContour* ); SkOpSegment* first() { SkASSERT(fCount > 0); return &fHead; } const SkOpSegment* first() const { SkASSERT(fCount > 0); return &fHead; } void indentDump() const { SkDEBUGCODE(fDebugIndent += 2); } void init(SkOpGlobalState* globalState, bool operand, bool isXor) { fState = globalState; fOperand = operand; fXor = isXor; SkDEBUGCODE(fID = globalState->nextContourID()); } int isCcw() const { return fCcw; } bool isXor() const { return fXor; } void joinSegments() { SkOpSegment* segment = &fHead; SkOpSegment* next; do { next = segment->next(); segment->joinEnds(next ? next : &fHead); } while ((segment = next)); } void markAllDone() { SkOpSegment* segment = &fHead; do { segment->markAllDone(); } while ((segment = segment->next())); } // Please keep this aligned with debugMissingCoincidence() bool missingCoincidence() { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; bool result = false; do { if (segment->missingCoincidence()) { result = true; } segment = segment->next(); } while (segment); return result; } bool moveMultiples() { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; do { if (!segment->moveMultiples()) { return false; } } while ((segment = segment->next())); return true; } bool moveNearby() { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; do { if (!segment->moveNearby()) { return false; } } while ((segment = segment->next())); return true; } SkOpContour* next() { return fNext; } const SkOpContour* next() const { return fNext; } bool operand() const { return fOperand; } bool oppXor() const { return fOppXor; } void outdentDump() const { SkDEBUGCODE(fDebugIndent -= 2); } void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkArenaAlloc*); void reset() { fTail = nullptr; fNext = nullptr; fCount = 0; fDone = false; SkDEBUGCODE(fBounds.setLTRB(SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin)); SkDEBUGCODE(fFirstSorted = -1); SkDEBUGCODE(fDebugIndent = 0); } void resetReverse() { SkOpContour* next = this; do { if (!next->count()) { continue; } next->fCcw = -1; next->fReverse = false; } while ((next = next->next())); } bool reversed() const { return fReverse; } void setBounds() { SkASSERT(fCount > 0); const SkOpSegment* segment = &fHead; fBounds = segment->bounds(); while ((segment = segment->next())) { fBounds.add(segment->bounds()); } } void setCcw(int ccw) { fCcw = ccw; } void setGlobalState(SkOpGlobalState* state) { fState = state; } void setNext(SkOpContour* contour) { // SkASSERT(!fNext == !!contour); fNext = contour; } void setOperand(bool isOp) { fOperand = isOp; } void setOppXor(bool isOppXor) { fOppXor = isOppXor; } void setReverse() { fReverse = true; } void setXor(bool isXor) { fXor = isXor; } bool sortAngles() { SkASSERT(fCount > 0); SkOpSegment* segment = &fHead; do { FAIL_IF(!segment->sortAngles()); } while ((segment = segment->next())); return true; } const SkPoint& start() const { return fHead.pts()[0]; } void toPartialBackward(SkPathWriter* path) const { const SkOpSegment* segment = fTail; do { SkAssertResult(segment->addCurveTo(segment->tail(), segment->head(), path)); } while ((segment = segment->prev())); } void toPartialForward(SkPathWriter* path) const { const SkOpSegment* segment = &fHead; do { SkAssertResult(segment->addCurveTo(segment->head(), segment->tail(), path)); } while ((segment = segment->next())); } void toReversePath(SkPathWriter* path) const; void toPath(SkPathWriter* path) const; SkOpSpan* undoneSpan(); protected: SkOpGlobalState* fState; SkOpSegment fHead; SkOpSegment* fTail; SkOpContour* fNext; SkPathOpsBounds fBounds; int fCcw; int fCount; int fFirstSorted; bool fDone; // set by find top segment bool fOperand; // true for the second argument to a binary operator bool fReverse; // true if contour should be reverse written to path (used only by fix winding) bool fXor; // set if original path had even-odd fill bool fOppXor; // set if opposite path had even-odd fill SkDEBUGCODE(int fID); SkDEBUGCODE(mutable int fDebugIndent); }; class SkOpContourHead : public SkOpContour { public: SkOpContour* appendContour() { SkOpContour* contour = this->globalState()->allocator()->make(); contour->setNext(nullptr); SkOpContour* prev = this; SkOpContour* next; while ((next = prev->next())) { prev = next; } prev->setNext(contour); return contour; } void joinAllSegments() { SkOpContour* next = this; do { if (!next->count()) { continue; } next->joinSegments(); } while ((next = next->next())); } void remove(SkOpContour* contour) { if (contour == this) { SkASSERT(this->count() == 0); return; } SkASSERT(contour->next() == nullptr); SkOpContour* prev = this; SkOpContour* next; while ((next = prev->next()) != contour) { SkASSERT(next); prev = next; } SkASSERT(prev); prev->setNext(nullptr); } }; class SkOpContourBuilder { public: SkOpContourBuilder(SkOpContour* contour) : fContour(contour) , fLastIsLine(false) { } void addConic(SkPoint pts[3], SkScalar weight); void addCubic(SkPoint pts[4]); void addCurve(SkPath::Verb verb, const SkPoint pts[4], SkScalar weight = 1); void addLine(const SkPoint pts[2]); void addQuad(SkPoint pts[3]); void flush(); SkOpContour* contour() { return fContour; } void setContour(SkOpContour* contour) { flush(); fContour = contour; } protected: SkOpContour* fContour; SkPoint fLastLine[2]; bool fLastIsLine; }; #ifdef SK_DEBUG #endif class SkIntersectionHelper { public: enum SegmentType { kHorizontalLine_Segment = -1, kVerticalLine_Segment = 0, kLine_Segment = SkPath::kLine_Verb, kQuad_Segment = SkPath::kQuad_Verb, kConic_Segment = SkPath::kConic_Verb, kCubic_Segment = SkPath::kCubic_Verb, }; bool advance() { fSegment = fSegment->next(); return fSegment != nullptr; } SkScalar bottom() const { return bounds().fBottom; } const SkPathOpsBounds& bounds() const { return fSegment->bounds(); } SkOpContour* contour() const { return fSegment->contour(); } void init(SkOpContour* contour) { fSegment = contour->first(); } SkScalar left() const { return bounds().fLeft; } const SkPoint* pts() const { return fSegment->pts(); } SkScalar right() const { return bounds().fRight; } SkOpSegment* segment() const { return fSegment; } SegmentType segmentType() const { SegmentType type = (SegmentType) fSegment->verb(); if (type != kLine_Segment) { return type; } if (fSegment->isHorizontal()) { return kHorizontalLine_Segment; } if (fSegment->isVertical()) { return kVerticalLine_Segment; } return kLine_Segment; } bool startAfter(const SkIntersectionHelper& after) { fSegment = after.fSegment->next(); return fSegment != nullptr; } SkScalar top() const { return bounds().fTop; } SkScalar weight() const { return fSegment->weight(); } SkScalar x() const { return bounds().fLeft; } bool xFlipped() const { return x() != pts()[0].fX; } SkScalar y() const { return bounds().fTop; } bool yFlipped() const { return y() != pts()[0].fY; } private: SkOpSegment* fSegment; }; class SkOpAngle; class SkOpContour; class SkOpSegment; template class SkTDArray; class SkCoincidentSpans { public: const SkOpPtT* coinPtTEnd() const; const SkOpPtT* coinPtTStart() const; // These return non-const pointers so that, as copies, they can be added // to a new span pair SkOpPtT* coinPtTEndWritable() const { return const_cast(fCoinPtTEnd); } SkOpPtT* coinPtTStartWritable() const { return const_cast(fCoinPtTStart); } bool collapsed(const SkOpPtT* ) const; bool contains(const SkOpPtT* s, const SkOpPtT* e) const; void correctEnds(); void correctOneEnd(const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, void (SkCoincidentSpans::* setEnd)(const SkOpPtT* ptT) ); #if DEBUG_COIN void debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const; void debugCorrectOneEnd(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, void (SkCoincidentSpans::* setEnd)(const SkOpPtT* ptT) const) const; bool debugExpand(SkPathOpsDebug::GlitchLog* log) const; #endif const char* debugID() const { #if DEBUG_COIN return fGlobalState->debugCoinDictEntry().fFunctionName; #else return nullptr; #endif } void debugShow() const; #ifdef SK_DEBUG void debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, const SkOpGlobalState* debugState) const; #endif void dump() const; bool expand(); bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); bool flipped() const { return fOppPtTStart->fT > fOppPtTEnd->fT; } SkDEBUGCODE(SkOpGlobalState* globalState() { return fGlobalState; }) void init(SkDEBUGCODE(SkOpGlobalState* globalState)) { sk_bzero(this, sizeof(*this)); SkDEBUGCODE(fGlobalState = globalState); } SkCoincidentSpans* next() { return fNext; } const SkCoincidentSpans* next() const { return fNext; } SkCoincidentSpans** nextPtr() { return &fNext; } const SkOpPtT* oppPtTStart() const; const SkOpPtT* oppPtTEnd() const; // These return non-const pointers so that, as copies, they can be added // to a new span pair SkOpPtT* oppPtTStartWritable() const { return const_cast(fOppPtTStart); } SkOpPtT* oppPtTEndWritable() const { return const_cast(fOppPtTEnd); } bool ordered(bool* result) const; void set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); void setCoinPtTEnd(const SkOpPtT* ptT) { SkOPASSERT(ptT == ptT->span()->ptT()); SkOPASSERT(!fCoinPtTStart || ptT->fT != fCoinPtTStart->fT); SkASSERT(!fCoinPtTStart || fCoinPtTStart->segment() == ptT->segment()); fCoinPtTEnd = ptT; ptT->setCoincident(); } void setCoinPtTStart(const SkOpPtT* ptT) { SkOPASSERT(ptT == ptT->span()->ptT()); SkOPASSERT(!fCoinPtTEnd || ptT->fT != fCoinPtTEnd->fT); SkASSERT(!fCoinPtTEnd || fCoinPtTEnd->segment() == ptT->segment()); fCoinPtTStart = ptT; ptT->setCoincident(); } void setEnds(const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTEnd) { this->setCoinPtTEnd(coinPtTEnd); this->setOppPtTEnd(oppPtTEnd); } void setOppPtTEnd(const SkOpPtT* ptT) { SkOPASSERT(ptT == ptT->span()->ptT()); SkOPASSERT(!fOppPtTStart || ptT->fT != fOppPtTStart->fT); SkASSERT(!fOppPtTStart || fOppPtTStart->segment() == ptT->segment()); fOppPtTEnd = ptT; ptT->setCoincident(); } void setOppPtTStart(const SkOpPtT* ptT) { SkOPASSERT(ptT == ptT->span()->ptT()); SkOPASSERT(!fOppPtTEnd || ptT->fT != fOppPtTEnd->fT); SkASSERT(!fOppPtTEnd || fOppPtTEnd->segment() == ptT->segment()); fOppPtTStart = ptT; ptT->setCoincident(); } void setStarts(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { this->setCoinPtTStart(coinPtTStart); this->setOppPtTStart(oppPtTStart); } void setNext(SkCoincidentSpans* next) { fNext = next; } private: SkCoincidentSpans* fNext; const SkOpPtT* fCoinPtTStart; const SkOpPtT* fCoinPtTEnd; const SkOpPtT* fOppPtTStart; const SkOpPtT* fOppPtTEnd; SkDEBUGCODE(SkOpGlobalState* fGlobalState); }; class SkOpCoincidence { public: SkOpCoincidence(SkOpGlobalState* globalState) : fHead(nullptr) , fTop(nullptr) , fGlobalState(globalState) , fContinue(false) , fSpanDeleted(false) , fPtAllocated(false) , fCoinExtended(false) , fSpanMerged(false) { globalState->setCoincidence(this); } void add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, SkOpPtT* oppPtTEnd); bool addEndMovedSpans(DEBUG_COIN_DECLARE_ONLY_PARAMS()); bool addExpanded(DEBUG_COIN_DECLARE_ONLY_PARAMS()); bool addMissing(bool* added DEBUG_COIN_DECLARE_PARAMS()); bool apply(DEBUG_COIN_DECLARE_ONLY_PARAMS()); bool contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const; void correctEnds(DEBUG_COIN_DECLARE_ONLY_PARAMS()); #if DEBUG_COIN void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log) const; void debugAddExpanded(SkPathOpsDebug::GlitchLog* ) const; void debugAddMissing(SkPathOpsDebug::GlitchLog* , bool* added) const; void debugAddOrOverlap(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, double coinTs, double coinTe, double oppTs, double oppTe, bool* added) const; #endif const SkOpAngle* debugAngle(int id) const { return SkDEBUGRELEASE(fGlobalState->debugAngle(id), nullptr); } void debugCheckBetween() const; #if DEBUG_COIN void debugCheckValid(SkPathOpsDebug::GlitchLog* log) const; #endif SkOpContour* debugContour(int id) const { return SkDEBUGRELEASE(fGlobalState->debugContour(id), nullptr); } #if DEBUG_COIN void debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const; bool debugExpand(SkPathOpsDebug::GlitchLog* ) const; void debugMark(SkPathOpsDebug::GlitchLog* ) const; void debugMarkCollapsed(SkPathOpsDebug::GlitchLog* , const SkCoincidentSpans* coin, const SkOpPtT* test) const; void debugMarkCollapsed(SkPathOpsDebug::GlitchLog* , const SkOpPtT* test) const; #endif const SkOpPtT* debugPtT(int id) const { return SkDEBUGRELEASE(fGlobalState->debugPtT(id), nullptr); } const SkOpSegment* debugSegment(int id) const { return SkDEBUGRELEASE(fGlobalState->debugSegment(id), nullptr); } #if DEBUG_COIN void debugRelease(SkPathOpsDebug::GlitchLog* , const SkCoincidentSpans* , const SkCoincidentSpans* ) const; void debugRelease(SkPathOpsDebug::GlitchLog* , const SkOpSegment* ) const; #endif void debugShowCoincidence() const; const SkOpSpanBase* debugSpan(int id) const { return SkDEBUGRELEASE(fGlobalState->debugSpan(id), nullptr); } void debugValidate() const; void dump() const; bool expand(DEBUG_COIN_DECLARE_ONLY_PARAMS()); bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); bool findOverlaps(SkOpCoincidence* DEBUG_COIN_DECLARE_PARAMS()) const; void fixUp(SkOpPtT* deleted, const SkOpPtT* kept); SkOpGlobalState* globalState() { return fGlobalState; } const SkOpGlobalState* globalState() const { return fGlobalState; } bool isEmpty() const { return !fHead && !fTop; } bool mark(DEBUG_COIN_DECLARE_ONLY_PARAMS()); void markCollapsed(SkOpPtT* ); static bool Ordered(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { return Ordered(coinPtTStart->segment(), oppPtTStart->segment()); } static bool Ordered(const SkOpSegment* coin, const SkOpSegment* opp); void release(const SkOpSegment* ); void releaseDeleted(); private: void add(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { this->add(const_cast(coinPtTStart), const_cast(coinPtTEnd), const_cast(oppPtTStart), const_cast(oppPtTEnd)); } bool addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan); bool addEndMovedSpans(const SkOpPtT* ptT); bool addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over2s, double tStart, double tEnd, SkOpSegment* coinSeg, SkOpSegment* oppSeg, bool* added SkDEBUGPARAMS(const SkOpPtT* over1e) SkDEBUGPARAMS(const SkOpPtT* over2e)); bool addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, double coinTs, double coinTe, double oppTs, double oppTe, bool* added); bool addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, const SkOpSegment* seg2, const SkOpSegment* seg2o, const SkOpPtT* overS, const SkOpPtT* overE); bool checkOverlap(SkCoincidentSpans* check, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, double coinTs, double coinTe, double oppTs, double oppTe, SkTDArray* overlaps) const; bool contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const; bool contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const; #if DEBUG_COIN void debugAddIfMissing(SkPathOpsDebug::GlitchLog* , const SkCoincidentSpans* outer, const SkOpPtT* over1s, const SkOpPtT* over1e) const; void debugAddIfMissing(SkPathOpsDebug::GlitchLog* , const SkOpPtT* over1s, const SkOpPtT* over2s, double tStart, double tEnd, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, bool* added, const SkOpPtT* over1e, const SkOpPtT* over2e) const; void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* , const SkOpSpan* base, const SkOpSpanBase* testSpan) const; void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* , const SkOpPtT* ptT) const; #endif void fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept); void markCollapsed(SkCoincidentSpans* head, SkOpPtT* test); bool overlap(const SkOpPtT* coinStart1, const SkOpPtT* coinEnd1, const SkOpPtT* coinStart2, const SkOpPtT* coinEnd2, double* overS, double* overE) const; bool release(SkCoincidentSpans* coin, SkCoincidentSpans* ); void releaseDeleted(SkCoincidentSpans* ); void restoreHead(); // return coinPtT->segment()->t mapped from overS->fT <= t <= overE->fT static double TRange(const SkOpPtT* overS, double t, const SkOpSegment* coinPtT SkDEBUGPARAMS(const SkOpPtT* overE)); SkCoincidentSpans* fHead; SkCoincidentSpans* fTop; SkOpGlobalState* fGlobalState; bool fContinue; bool fSpanDeleted; bool fPtAllocated; bool fCoinExtended; bool fSpanMerged; }; #ifndef SkPathWriter_DEFINED #define SkPathWriter_DEFINED class SkOpPtT; // Construct the path one contour at a time. // If the contour is closed, copy it to the final output. // Otherwise, keep the partial contour for later assembly. class SkPathWriter { public: SkPathWriter(SkPath& path); void assemble(); void conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight); void cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3); bool deferredLine(const SkOpPtT* pt); void deferredMove(const SkOpPtT* pt); void finishContour(); bool hasMove() const { return !fFirstPtT; } void init(); bool isClosed() const; const SkPath* nativePath() const { return fPathPtr; } void quadTo(const SkPoint& pt1, const SkOpPtT* pt2); private: bool changedSlopes(const SkOpPtT* pt) const; void close(); const SkTDArray& endPtTs() const { return fEndPtTs; } void lineTo(); bool matchedLast(const SkOpPtT*) const; void moveTo(); const skia_private::TArray& partials() const { return fPartials; } bool someAssemblyRequired(); SkPoint update(const SkOpPtT* pt); SkPath fCurrent; // contour under construction skia_private::TArray fPartials; // contours with mismatched starts and ends SkTDArray fEndPtTs; // possible pt values for partial starts and ends SkPath* fPathPtr; // closed contours are written here const SkOpPtT* fDefer[2]; // [0] deferred move, [1] deferred line const SkOpPtT* fFirstPtT; // first in current contour }; #endif /* defined(__PathOps__SkPathWriter__) */ class SkPath; class SkOpEdgeBuilder { public: SkOpEdgeBuilder(const SkPathWriter& path, SkOpContourHead* contours2, SkOpGlobalState* globalState) : fGlobalState(globalState) , fPath(path.nativePath()) , fContourBuilder(contours2) , fContoursHead(contours2) , fAllowOpenContours(true) { init(); } SkOpEdgeBuilder(const SkPath& path, SkOpContourHead* contours2, SkOpGlobalState* globalState) : fGlobalState(globalState) , fPath(&path) , fContourBuilder(contours2) , fContoursHead(contours2) , fAllowOpenContours(false) { init(); } void addOperand(const SkPath& path); void complete() { fContourBuilder.flush(); SkOpContour* contour = fContourBuilder.contour(); if (contour && contour->count()) { contour->complete(); fContourBuilder.setContour(nullptr); } } bool finish(); const SkOpContour* head() const { return fContoursHead; } void init(); bool unparseable() const { return fUnparseable; } SkPathOpsMask xorMask() const { return fXorMask[fOperand]; } private: void closeContour(const SkPoint& curveEnd, const SkPoint& curveStart); bool close(); int preFetch(); bool walk(); SkOpGlobalState* fGlobalState; const SkPath* fPath; SkTDArray fPathPts; SkTDArray fWeights; SkTDArray fPathVerbs; SkOpContourBuilder fContourBuilder; SkOpContourHead* fContoursHead; SkPathOpsMask fXorMask[2]; int fSecondHalf; bool fOperand; bool fAllowOpenContours; bool fUnparseable; }; struct SkConic; struct SkPoint; union SkReduceOrder { enum Quadratics { kNo_Quadratics, kAllow_Quadratics }; int reduce(const SkDCubic& cubic, Quadratics); int reduce(const SkDLine& line); int reduce(const SkDQuad& quad); static SkPath::Verb Conic(const SkConic& conic, SkPoint* reducePts); static SkPath::Verb Cubic(const SkPoint pts[4], SkPoint* reducePts); static SkPath::Verb Quad(const SkPoint pts[3], SkPoint* reducePts); SkDLine fLine; SkDQuad fQuad; SkDCubic fCubic; }; class SkOpAngle; class SkOpCoincidence; class SkOpContourHead; class SkOpSegment; class SkOpSpan; class SkOpSpanBase; class SkPath; template class SkTDArray; const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr, bool* sortable); SkOpSegment* FindChase(SkTDArray* chase, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr); SkOpSpan* FindSortableTop(SkOpContourHead* ); SkOpSpan* FindUndone(SkOpContourHead* ); bool FixWinding(SkPath* path); bool SortContourList(SkOpContourHead** , bool evenOdd, bool oppEvenOdd); bool HandleCoincidence(SkOpContourHead* , SkOpCoincidence* ); bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)); class SkIntersections; class SkTSect; class SkTSpan; struct SkDLine; #ifdef SK_DEBUG typedef uint8_t SkOpDebugBool; #else typedef bool SkOpDebugBool; #endif static inline bool SkDoubleIsNaN(double x) { return x != x; } class SkTCoincident { public: SkTCoincident() { this->init(); } void debugInit() { #ifdef SK_DEBUG this->fPerpPt.fX = this->fPerpPt.fY = SK_ScalarNaN; this->fPerpT = SK_ScalarNaN; this->fMatch = 0xFF; #endif } char dumpIsCoincidentStr() const; void dump() const; bool isMatch() const { SkASSERT(!!fMatch == fMatch); return SkToBool(fMatch); } void init() { fPerpT = -1; fMatch = false; fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN; } void markCoincident() { if (!fMatch) { fPerpT = -1; } fMatch = true; } const SkDPoint& perpPt() const { return fPerpPt; } double perpT() const { return fPerpT; } void setPerp(const SkTCurve& c1, double t, const SkDPoint& cPt, const SkTCurve& ); private: SkDPoint fPerpPt; double fPerpT; // perpendicular intersection on opposite curve SkOpDebugBool fMatch; }; struct SkTSpanBounded { SkTSpan* fBounded; SkTSpanBounded* fNext; }; class SkTSpan { public: SkTSpan(const SkTCurve& curve, SkArenaAlloc& heap) { fPart = curve.make(heap); } void addBounded(SkTSpan* , SkArenaAlloc* ); double closestBoundedT(const SkDPoint& pt) const; bool contains(double t) const; void debugInit(const SkTCurve& curve, SkArenaAlloc& heap) { #ifdef SK_DEBUG SkTCurve* fake = curve.make(heap); fake->debugInit(); init(*fake); initBounds(*fake); fCoinStart.init(); fCoinEnd.init(); #endif } const SkTSect* debugOpp() const; #ifdef SK_DEBUG void debugSetGlobalState(SkOpGlobalState* state) { fDebugGlobalState = state; } const SkTSpan* debugSpan(int ) const; const SkTSpan* debugT(double t) const; bool debugIsBefore(const SkTSpan* span) const; #endif void dump() const; void dumpAll() const; void dumpBounded(int id) const; void dumpBounds() const; void dumpCoin() const; double endT() const { return fEndT; } SkTSpan* findOppSpan(const SkTSpan* opp) const; SkTSpan* findOppT(double t) const { SkTSpan* result = oppT(t); SkOPASSERT(result); return result; } SkDEBUGCODE(SkOpGlobalState* globalState() const { return fDebugGlobalState; }) bool hasOppT(double t) const { return SkToBool(oppT(t)); } int hullsIntersect(SkTSpan* span, bool* start, bool* oppStart); void init(const SkTCurve& ); bool initBounds(const SkTCurve& ); bool isBounded() const { return fBounded != nullptr; } bool linearsIntersect(SkTSpan* span); double linearT(const SkDPoint& ) const; void markCoincident() { fCoinStart.markCoincident(); fCoinEnd.markCoincident(); } const SkTSpan* next() const { return fNext; } bool onlyEndPointsInCommon(const SkTSpan* opp, bool* start, bool* oppStart, bool* ptsInCommon); const SkTCurve& part() const { return *fPart; } int pointCount() const { return fPart->pointCount(); } const SkDPoint& pointFirst() const { return (*fPart)[0]; } const SkDPoint& pointLast() const { return (*fPart)[fPart->pointLast()]; } bool removeAllBounded(); bool removeBounded(const SkTSpan* opp); void reset() { fBounded = nullptr; } void resetBounds(const SkTCurve& curve) { fIsLinear = fIsLine = false; initBounds(curve); } bool split(SkTSpan* work, SkArenaAlloc* heap) { return splitAt(work, (work->fStartT + work->fEndT) * 0.5, heap); } bool splitAt(SkTSpan* work, double t, SkArenaAlloc* heap); double startT() const { return fStartT; } private: // implementation is for testing only int debugID() const { return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); } void dumpID() const; int hullCheck(const SkTSpan* opp, bool* start, bool* oppStart); int linearIntersects(const SkTCurve& ) const; SkTSpan* oppT(double t) const; void validate() const; void validateBounded() const; void validatePerpT(double oppT) const; void validatePerpPt(double t, const SkDPoint& ) const; SkTCurve* fPart; SkTCoincident fCoinStart; SkTCoincident fCoinEnd; SkTSpanBounded* fBounded; SkTSpan* fPrev; SkTSpan* fNext; SkDRect fBounds; double fStartT; double fEndT; double fBoundsMax; SkOpDebugBool fCollapsed; SkOpDebugBool fHasPerp; SkOpDebugBool fIsLinear; SkOpDebugBool fIsLine; SkOpDebugBool fDeleted; SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); SkDEBUGCODE(SkTSect* fDebugSect); PATH_OPS_DEBUG_T_SECT_CODE(int fID); friend class SkTSect; }; class SkTSect { public: SkTSect(const SkTCurve& c SkDEBUGPARAMS(SkOpGlobalState* ) PATH_OPS_DEBUG_T_SECT_PARAMS(int id)); static void BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersections* intersections); SkDEBUGCODE(SkOpGlobalState* globalState() { return fDebugGlobalState; }) bool hasBounded(const SkTSpan* ) const; const SkTSect* debugOpp() const { return SkDEBUGRELEASE(fOppSect, nullptr); } #ifdef SK_DEBUG const SkTSpan* debugSpan(int id) const; const SkTSpan* debugT(double t) const; #endif void dump() const; void dumpBoth(SkTSect* ) const; void dumpBounded(int id) const; void dumpBounds() const; void dumpCoin() const; void dumpCoinCurves() const; void dumpCurves() const; private: enum { kZeroS1Set = 1, kOneS1Set = 2, kZeroS2Set = 4, kOneS2Set = 8 }; SkTSpan* addFollowing(SkTSpan* prior); void addForPerp(SkTSpan* span, double t); SkTSpan* addOne(); SkTSpan* addSplitAt(SkTSpan* span, double t) { SkTSpan* result = this->addOne(); SkDEBUGCODE(result->debugSetGlobalState(this->globalState())); result->splitAt(span, t, &fHeap); result->initBounds(fCurve); span->initBounds(fCurve); return result; } bool binarySearchCoin(SkTSect* , double tStart, double tStep, double* t, double* oppT, SkTSpan** oppFirst); SkTSpan* boundsMax(); bool coincidentCheck(SkTSect* sect2); void coincidentForce(SkTSect* sect2, double start1s, double start1e); bool coincidentHasT(double t); int collapsed() const; void computePerpendiculars(SkTSect* sect2, SkTSpan* first, SkTSpan* last); int countConsecutiveSpans(SkTSpan* first, SkTSpan** last) const; int debugID() const { return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); } bool deleteEmptySpans(); void dumpCommon(const SkTSpan* ) const; void dumpCommonCurves(const SkTSpan* ) const; static int EndsEqual(const SkTSect* sect1, const SkTSect* sect2, SkIntersections* ); bool extractCoincident(SkTSect* sect2, SkTSpan* first, SkTSpan* last, SkTSpan** result); SkTSpan* findCoincidentRun(SkTSpan* first, SkTSpan** lastPtr); int intersects(SkTSpan* span, SkTSect* opp, SkTSpan* oppSpan, int* oppResult); bool isParallel(const SkDLine& thisLine, const SkTSect* opp) const; int linesIntersect(SkTSpan* span, SkTSect* opp, SkTSpan* oppSpan, SkIntersections* ); bool markSpanGone(SkTSpan* span); bool matchedDirection(double t, const SkTSect* sect2, double t2) const; void matchedDirCheck(double t, const SkTSect* sect2, double t2, bool* calcMatched, bool* oppMatched) const; void mergeCoincidence(SkTSect* sect2); const SkDPoint& pointLast() const { return fCurve[fCurve.pointLast()]; } SkTSpan* prev(SkTSpan* ) const; bool removeByPerpendicular(SkTSect* opp); void recoverCollapsed(); bool removeCoincident(SkTSpan* span, bool isBetween); void removeAllBut(const SkTSpan* keep, SkTSpan* span, SkTSect* opp); bool removeSpan(SkTSpan* span); void removeSpanRange(SkTSpan* first, SkTSpan* last); bool removeSpans(SkTSpan* span, SkTSect* opp); void removedEndCheck(SkTSpan* span); void resetRemovedEnds() { fRemovedStartT = fRemovedEndT = false; } SkTSpan* spanAtT(double t, SkTSpan** priorSpan); SkTSpan* tail(); bool trim(SkTSpan* span, SkTSect* opp); bool unlinkSpan(SkTSpan* span); bool updateBounded(SkTSpan* first, SkTSpan* last, SkTSpan* oppFirst); void validate() const; void validateBounded() const; const SkTCurve& fCurve; SkSTArenaAlloc<1024> fHeap; SkTSpan* fHead; SkTSpan* fCoincident; SkTSpan* fDeleted; int fActiveCount; bool fRemovedStartT; bool fRemovedEndT; bool fHung; SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); SkDEBUGCODE(SkTSect* fOppSect); PATH_OPS_DEBUG_T_SECT_CODE(int fID); PATH_OPS_DEBUG_T_SECT_CODE(int fDebugCount); #if DEBUG_T_SECT int fDebugAllocatedCount; #endif friend class SkTSpan; }; static char* end_chain(char*) { return nullptr; } SkArenaAlloc::SkArenaAlloc(char* block, size_t size, size_t firstHeapAllocation) : fDtorCursor {block} , fCursor {block} , fEnd {block + SkToU32(size)} , fFibonacciProgression{SkToU32(size), SkToU32(firstHeapAllocation)} { if (size < sizeof(Footer)) { fEnd = fCursor = fDtorCursor = nullptr; } if (fCursor != nullptr) { this->installFooter(end_chain, 0); sk_asan_poison_memory_region(fCursor, fEnd - fCursor); } } SkArenaAlloc::~SkArenaAlloc() { RunDtorsOnBlock(fDtorCursor); } void SkArenaAlloc::installFooter(FooterAction* action, uint32_t padding) { assert(SkTFitsIn(padding)); this->installRaw(action); this->installRaw((uint8_t)padding); fDtorCursor = fCursor; } char* SkArenaAlloc::SkipPod(char* footerEnd) { char* objEnd = footerEnd - (sizeof(Footer) + sizeof(uint32_t)); uint32_t skip; memmove(&skip, objEnd, sizeof(uint32_t)); return objEnd - (ptrdiff_t) skip; } void SkArenaAlloc::RunDtorsOnBlock(char* footerEnd) { while (footerEnd != nullptr) { FooterAction* action; uint8_t padding; memcpy(&action, footerEnd - sizeof( Footer), sizeof( action)); memcpy(&padding, footerEnd - sizeof(padding), sizeof(padding)); footerEnd = action(footerEnd) - (ptrdiff_t)padding; } } char* SkArenaAlloc::NextBlock(char* footerEnd) { char* objEnd = footerEnd - (sizeof(char*) + sizeof(Footer)); char* next; memmove(&next, objEnd, sizeof(char*)); RunDtorsOnBlock(next); sk_free(objEnd); return nullptr; } void SkArenaAlloc::ensureSpace(uint32_t size, uint32_t alignment) { constexpr uint32_t headerSize = sizeof(Footer) + sizeof(ptrdiff_t); constexpr uint32_t maxSize = std::numeric_limits::max(); constexpr uint32_t overhead = headerSize + sizeof(Footer); AssertRelease(size <= maxSize - overhead); uint32_t objSizeAndOverhead = size + overhead; const uint32_t alignmentOverhead = alignment - 1; AssertRelease(objSizeAndOverhead <= maxSize - alignmentOverhead); objSizeAndOverhead += alignmentOverhead; uint32_t minAllocationSize = fFibonacciProgression.nextBlockSize(); uint32_t allocationSize = std::max(objSizeAndOverhead, minAllocationSize); // Round up to a nice size. If > 32K align to 4K boundary else up to max_align_t. The > 32K // heuristic is from the JEMalloc behavior. { uint32_t mask = allocationSize > (1 << 15) ? (1 << 12) - 1 : 16 - 1; AssertRelease(allocationSize <= maxSize - mask); allocationSize = (allocationSize + mask) & ~mask; } char* newBlock = static_cast(sk_malloc_throw(allocationSize)); auto previousDtor = fDtorCursor; fCursor = newBlock; fDtorCursor = newBlock; fEnd = fCursor + allocationSize; // poison the unused bytes in the block. sk_asan_poison_memory_region(fCursor, fEnd - fCursor); this->installRaw(previousDtor); this->installFooter(NextBlock, 0); } char* SkArenaAlloc::allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment) { uintptr_t mask = alignment - 1; restart: uint32_t skipOverhead = 0; const bool needsSkipFooter = fCursor != fDtorCursor; if (needsSkipFooter) { skipOverhead = sizeof(Footer) + sizeof(uint32_t); } const uint32_t totalSize = sizeIncludingFooter + skipOverhead; // Math on null fCursor/fEnd is undefined behavior, so explicitly check for first alloc. if (!fCursor) { this->ensureSpace(totalSize, alignment); goto restart; } assert(fEnd); // This test alone would be enough nullptr were defined to be 0, but it's not. char* objStart = (char*)((uintptr_t)(fCursor + skipOverhead + mask) & ~mask); if ((ptrdiff_t)totalSize > fEnd - objStart) { this->ensureSpace(totalSize, alignment); goto restart; } AssertRelease((ptrdiff_t)totalSize <= fEnd - objStart); // Install a skip footer if needed, thus terminating a run of POD data. The calling code is // responsible for installing the footer after the object. if (needsSkipFooter) { this->installRaw(SkToU32(fCursor - fDtorCursor)); this->installFooter(SkipPod, 0); } return objStart; } SkArenaAllocWithReset::SkArenaAllocWithReset(char* block, size_t size, size_t firstHeapAllocation) : SkArenaAlloc(block, size, firstHeapAllocation) , fFirstBlock{block} , fFirstSize{SkToU32(size)} , fFirstHeapAllocationSize{SkToU32(firstHeapAllocation)} {} void SkArenaAllocWithReset::reset() { char* const firstBlock = fFirstBlock; const uint32_t firstSize = fFirstSize; const uint32_t firstHeapAllocationSize = fFirstHeapAllocationSize; this->~SkArenaAllocWithReset(); new (this) SkArenaAllocWithReset{firstBlock, firstSize, firstHeapAllocationSize}; } bool SkArenaAllocWithReset::isEmpty() { return this->cursor() == nullptr || this->cursor() == fFirstBlock + sizeof(Footer); } // SkFibonacci47 is the first 47 Fibonacci numbers. Fib(47) is the largest value less than 2 ^ 32. // Used by SkFibBlockSizes. std::array SkFibonacci47 { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, }; static inline double interpolate(double A, double B, double t) { return A + (B - A) * t; } std::array SkBezierCubic::EvalAt(const double curve[8], double t) { const auto in_X = [&curve](size_t n) { return curve[2*n]; }; const auto in_Y = [&curve](size_t n) { return curve[2*n + 1]; }; // Two semi-common fast paths if (t == 0) { return {in_X(0), in_Y(0)}; } if (t == 1) { return {in_X(3), in_Y(3)}; } // X(t) = X_0*(1-t)^3 + 3*X_1*t(1-t)^2 + 3*X_2*t^2(1-t) + X_3*t^3 // Y(t) = Y_0*(1-t)^3 + 3*Y_1*t(1-t)^2 + 3*Y_2*t^2(1-t) + Y_3*t^3 // Some compilers are smart enough and have sufficient registers/intrinsics to write optimal // code from // double one_minus_t = 1 - t; // double a = one_minus_t * one_minus_t * one_minus_t; // double b = 3 * one_minus_t * one_minus_t * t; // double c = 3 * one_minus_t * t * t; // double d = t * t * t; // However, some (e.g. when compiling for ARM) fail to do so, so we use this form // to help more compilers generate smaller/faster ASM. https://godbolt.org/z/M6jG9x45c double one_minus_t = 1 - t; double one_minus_t_squared = one_minus_t * one_minus_t; double a = (one_minus_t_squared * one_minus_t); double b = 3 * one_minus_t_squared * t; double t_squared = t * t; double c = 3 * one_minus_t * t_squared; double d = t_squared * t; return {a * in_X(0) + b * in_X(1) + c * in_X(2) + d * in_X(3), a * in_Y(0) + b * in_Y(1) + c * in_Y(2) + d * in_Y(3)}; } // Perform subdivision using De Casteljau's algorithm, that is, repeated linear // interpolation between adjacent points. void SkBezierCubic::Subdivide(const double curve[8], double t, double twoCurves[14]) { SkASSERT(0.0 <= t && t <= 1.0); // We split the curve "in" into two curves "alpha" and "beta" const auto in_X = [&curve](size_t n) { return curve[2*n]; }; const auto in_Y = [&curve](size_t n) { return curve[2*n + 1]; }; const auto alpha_X = [&twoCurves](size_t n) -> double& { return twoCurves[2*n]; }; const auto alpha_Y = [&twoCurves](size_t n) -> double& { return twoCurves[2*n + 1]; }; const auto beta_X = [&twoCurves](size_t n) -> double& { return twoCurves[2*n + 6]; }; const auto beta_Y = [&twoCurves](size_t n) -> double& { return twoCurves[2*n + 7]; }; alpha_X(0) = in_X(0); alpha_Y(0) = in_Y(0); beta_X(3) = in_X(3); beta_Y(3) = in_Y(3); double x01 = interpolate(in_X(0), in_X(1), t); double y01 = interpolate(in_Y(0), in_Y(1), t); double x12 = interpolate(in_X(1), in_X(2), t); double y12 = interpolate(in_Y(1), in_Y(2), t); double x23 = interpolate(in_X(2), in_X(3), t); double y23 = interpolate(in_Y(2), in_Y(3), t); alpha_X(1) = x01; alpha_Y(1) = y01; beta_X(2) = x23; beta_Y(2) = y23; alpha_X(2) = interpolate(x01, x12, t); alpha_Y(2) = interpolate(y01, y12, t); beta_X(1) = interpolate(x12, x23, t); beta_Y(1) = interpolate(y12, y23, t); alpha_X(3) /*= beta_X(0) */ = interpolate(alpha_X(2), beta_X(1), t); alpha_Y(3) /*= beta_Y(0) */ = interpolate(alpha_Y(2), beta_Y(1), t); } std::array SkBezierCubic::ConvertToPolynomial(const double curve[8], bool yValues) { const double* offset_curve = yValues ? curve + 1 : curve; const auto P = [&offset_curve](size_t n) { return offset_curve[2*n]; }; // A cubic Bézier curve is interpolated as follows: // c(t) = (1 - t)^3 P_0 + 3t(1 - t)^2 P_1 + 3t^2 (1 - t) P_2 + t^3 P_3 // = (-P_0 + 3P_1 + -3P_2 + P_3) t^3 + (3P_0 - 6P_1 + 3P_2) t^2 + // (-3P_0 + 3P_1) t + P_0 // Where P_N is the Nth point. The second step expands the polynomial and groups // by powers of t. The desired output is a cubic formula, so we just need to // combine the appropriate points to make the coefficients. std::array results; results[0] = -P(0) + 3*P(1) - 3*P(2) + P(3); results[1] = 3*P(0) - 6*P(1) + 3*P(2); results[2] = -3*P(0) + 3*P(1); results[3] = P(0); return results; } namespace { struct DPoint { DPoint(double x_, double y_) : x{x_}, y{y_} {} DPoint(SkPoint p) : x{p.fX}, y{p.fY} {} double x, y; }; DPoint operator- (DPoint a) { return {-a.x, -a.y}; } DPoint operator+ (DPoint a, DPoint b) { return {a.x + b.x, a.y + b.y}; } DPoint operator- (DPoint a, DPoint b) { return {a.x - b.x, a.y - b.y}; } DPoint operator* (double s, DPoint a) { return {s * a.x, s * a.y}; } // Pin to 0 or 1 if within half a float ulp of 0 or 1. double pinTRange(double t) { // The ULPs around 0 are tiny compared to the ULPs around 1. Shift to 1 to use the same // size ULPs. if (sk_double_to_float(t + 1.0) == 1.0f) { return 0.0; } else if (sk_double_to_float(t) == 1.0f) { return 1.0; } return t; } } // namespace SkSpan SkBezierCubic::IntersectWithHorizontalLine( SkSpan controlPoints, float yIntercept, float* intersectionStorage) { SkASSERT(controlPoints.size() >= 4); const DPoint P0 = controlPoints[0], P1 = controlPoints[1], P2 = controlPoints[2], P3 = controlPoints[3]; const DPoint A = -P0 + 3*P1 - 3*P2 + P3, B = 3*P0 - 6*P1 + 3*P2, C = -3*P0 + 3*P1, D = P0; return Intersect(A.x, B.x, C.x, D.x, A.y, B.y, C.y, D.y, yIntercept, intersectionStorage); } SkSpan SkBezierCubic::Intersect(double AX, double BX, double CX, double DX, double AY, double BY, double CY, double DY, float toIntersect, float intersectionsStorage[3]) { double roots[3]; SkSpan ts = SkSpan(roots, SkCubics::RootsReal(AY, BY, CY, DY - toIntersect, roots)); int intersectionCount = 0; for (double t : ts) { const double pinnedT = pinTRange(t); if (0 <= pinnedT && pinnedT <= 1) { intersectionsStorage[intersectionCount++] = SkCubics::EvalAt(AX, BX, CX, DX, pinnedT); } } return {intersectionsStorage, intersectionCount}; } SkSpan SkBezierQuad::IntersectWithHorizontalLine(SkSpan controlPoints, float yIntercept, float intersectionStorage[2]) { SkASSERT(controlPoints.size() >= 3); const DPoint p0 = controlPoints[0], p1 = controlPoints[1], p2 = controlPoints[2]; // Calculate A, B, C using doubles to reduce round-off error. const DPoint A = p0 - 2 * p1 + p2, // Remember we are generating the polynomial in the form A*t^2 -2*B*t + C, so the factor // of 2 is not needed and the term is negated. This term for a Bézier curve is usually // 2(p1-p0). B = p0 - p1, C = p0; return Intersect(A.x, B.x, C.x, A.y, B.y, C.y, yIntercept, intersectionStorage); } SkSpan SkBezierQuad::Intersect( double AX, double BX, double CX, double AY, double BY, double CY, double yIntercept, float intersectionStorage[2]) { auto [discriminant, r0, r1] = SkQuads::Roots(AY, BY, CY - yIntercept); int intersectionCount = 0; // Round the roots to the nearest float to generate the values t. Valid t's are on the // domain [0, 1]. const double t0 = pinTRange(r0); if (0 <= t0 && t0 <= 1) { intersectionStorage[intersectionCount++] = SkQuads::EvalAt(AX, -2 * BX, CX, t0); } const double t1 = pinTRange(r1); if (0 <= t1 && t1 <= 1 && t1 != t0) { intersectionStorage[intersectionCount++] = SkQuads::EvalAt(AX, -2 * BX, CX, t1); } return SkSpan{intersectionStorage, intersectionCount}; } /////////////////////////////////////////////////////////////////////////////////////////////////// const void* SkRBuffer::skip(size_t size) { if (fValid && size <= this->available()) { const void* pos = fPos; fPos += size; return pos; } fValid = false; return nullptr; } bool SkRBuffer::read(void* buffer, size_t size) { if (const void* src = this->skip(size)) { sk_careful_memcpy(buffer, src, size); return true; } return false; } bool SkRBuffer::skipToAlign4() { intptr_t pos = reinterpret_cast(fPos); size_t n = SkAlign4(pos) - pos; if (fValid && n <= this->available()) { fPos += n; return true; } else { fValid = false; return false; } } /////////////////////////////////////////////////////////////////////////////////////////////////// void* SkWBuffer::skip(size_t size) { void* result = fPos; writeNoSizeCheck(nullptr, size); return fData == nullptr ? nullptr : result; } void SkWBuffer::writeNoSizeCheck(const void* buffer, size_t size) { SkASSERT(fData == nullptr || fStop == nullptr || fPos + size <= fStop); if (fData && buffer) { sk_careful_memcpy(fPos, buffer, size); } fPos += size; } size_t SkWBuffer::padToAlign4() { size_t pos = this->pos(); size_t n = SkAlign4(pos) - pos; if (n && fData) { char* p = fPos; char* stop = p + n; do { *p++ = 0; } while (p < stop); } fPos += n; return n; } #if 0 #ifdef SK_DEBUG static void AssertBuffer32(const void* buffer) { SkASSERT(buffer); SkASSERT(((size_t)buffer & 3) == 0); } #else #define AssertBuffer32(buffer) #endif #endif // Copyright 2019 Google LLC. // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) #elif defined(SK_BUILD_FOR_ANDROID) || defined(SK_BUILD_FOR_UNIX) #elif defined(SK_BUILD_FOR_WIN) #endif namespace { // Return at least as many bytes to keep malloc aligned. constexpr size_t kMinBytes = alignof(max_align_t); SkSpan complete_size(void* ptr, size_t size) { if (ptr == nullptr) { return {}; } size_t completeSize = size; // Use the OS specific calls to find the actual capacity. #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) // TODO: remove the max, when the chrome implementation of malloc_size doesn't return 0. completeSize = std::max(malloc_size(ptr), size); #elif defined(SK_BUILD_FOR_ANDROID) && __ANDROID_API__ >= 17 completeSize = malloc_usable_size(ptr); SkASSERT(completeSize >= size); #elif defined(SK_BUILD_FOR_UNIX) completeSize = malloc_usable_size(ptr); SkASSERT(completeSize >= size); #elif defined(SK_BUILD_FOR_WIN) completeSize = _msize(ptr); SkASSERT(completeSize >= size); #endif return {static_cast(ptr), completeSize}; } } // namespace SkSpan SkContainerAllocator::allocate(int capacity, double growthFactor) { SkASSERT(capacity >= 0); SkASSERT(growthFactor >= 1.0); SkASSERT_RELEASE(capacity <= fMaxCapacity); if (growthFactor > 1.0 && capacity > 0) { capacity = this->growthFactorCapacity(capacity, growthFactor); } return sk_allocate_throw(capacity * fSizeOfT); } size_t SkContainerAllocator::roundUpCapacity(int64_t capacity) const { SkASSERT(capacity >= 0); // If round will not go above fMaxCapacity return rounded capacity. if (capacity < fMaxCapacity - kCapacityMultiple) { return SkAlignTo(capacity, kCapacityMultiple); } return SkToSizeT(fMaxCapacity); } size_t SkContainerAllocator::growthFactorCapacity(int capacity, double growthFactor) const { SkASSERT(capacity >= 0); SkASSERT(growthFactor >= 1.0); // Multiply by the growthFactor. Remember this must be done in 64-bit ints and not // size_t because size_t changes. const int64_t capacityGrowth = static_cast(capacity * growthFactor); // Notice that for small values of capacity, rounding up will provide most of the growth. return this->roundUpCapacity(capacityGrowth); } SkSpan sk_allocate_canfail(size_t size) { // Make sure to ask for at least the minimum number of bytes. const size_t adjustedSize = std::max(size, kMinBytes); void* ptr = sk_malloc_canfail(adjustedSize); return complete_size(ptr, adjustedSize); } SkSpan sk_allocate_throw(size_t size) { if (size == 0) { return {}; } // Make sure to ask for at least the minimum number of bytes. const size_t adjustedSize = std::max(size, kMinBytes); void* ptr = sk_malloc_throw(adjustedSize); return complete_size(ptr, adjustedSize); } void sk_report_container_overflow_and_die() { SK_ABORT("Requested capacity is too large."); } static constexpr double PI = 3.141592653589793; static bool nearly_equal(double x, double y) { if (sk_double_nearly_zero(x)) { return sk_double_nearly_zero(y); } return sk_doubles_nearly_equal_ulps(x, y); } // When the A coefficient of a cubic is close to 0, there can be floating point error // that arises from computing a very large root. In those cases, we would rather be // precise about the smaller 2 roots, so we have this arbitrary cutoff for when A is // really small or small compared to B. static bool close_to_a_quadratic(double A, double B) { if (sk_double_nearly_zero(B)) { return sk_double_nearly_zero(A); } return std::abs(A / B) < 1.0e-7; } int SkCubics::RootsReal(double A, double B, double C, double D, double solution[3]) { if (close_to_a_quadratic(A, B)) { return SkQuads::RootsReal(B, C, D, solution); } if (sk_double_nearly_zero(D)) { // 0 is one root int num = SkQuads::RootsReal(A, B, C, solution); for (int i = 0; i < num; ++i) { if (sk_double_nearly_zero(solution[i])) { return num; } } solution[num++] = 0; return num; } if (sk_double_nearly_zero(A + B + C + D)) { // 1 is one root int num = SkQuads::RootsReal(A, A + B, -D, solution); for (int i = 0; i < num; ++i) { if (sk_doubles_nearly_equal_ulps(solution[i], 1)) { return num; } } solution[num++] = 1; return num; } double a, b, c; { // If A is zero (e.g. B was nan and thus close_to_a_quadratic was false), we will // temporarily have infinities rolling about, but will catch that when checking // R2MinusQ3. double invA = sk_ieee_double_divide(1, A); a = B * invA; b = C * invA; c = D * invA; } double a2 = a * a; double Q = (a2 - b * 3) / 9; double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; double R2 = R * R; double Q3 = Q * Q * Q; double R2MinusQ3 = R2 - Q3; // If one of R2 Q3 is infinite or nan, subtracting them will also be infinite/nan. // If both are infinite or nan, the subtraction will be nan. // In either case, we have no finite roots. if (!std::isfinite(R2MinusQ3)) { return 0; } double adiv3 = a / 3; double r; double* roots = solution; if (R2MinusQ3 < 0) { // we have 3 real roots // the divide/root can, due to finite precisions, be slightly outside of -1...1 const double theta = acos(SkTPin(R / std::sqrt(Q3), -1., 1.)); const double neg2RootQ = -2 * std::sqrt(Q); r = neg2RootQ * cos(theta / 3) - adiv3; *roots++ = r; r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3; if (!nearly_equal(solution[0], r)) { *roots++ = r; } r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3; if (!nearly_equal(solution[0], r) && (roots - solution == 1 || !nearly_equal(solution[1], r))) { *roots++ = r; } } else { // we have 1 real root const double sqrtR2MinusQ3 = std::sqrt(R2MinusQ3); A = fabs(R) + sqrtR2MinusQ3; A = std::cbrt(A); // cube root if (R > 0) { A = -A; } if (!sk_double_nearly_zero(A)) { A += Q / A; } r = A - adiv3; *roots++ = r; if (!sk_double_nearly_zero(R2) && sk_doubles_nearly_equal_ulps(R2, Q3)) { r = -A / 2 - adiv3; if (!nearly_equal(solution[0], r)) { *roots++ = r; } } } return static_cast(roots - solution); } int SkCubics::RootsValidT(double A, double B, double C, double D, double solution[3]) { double allRoots[3] = {0, 0, 0}; int realRoots = SkCubics::RootsReal(A, B, C, D, allRoots); int foundRoots = 0; for (int index = 0; index < realRoots; ++index) { double tValue = allRoots[index]; if (tValue >= 1.0 && tValue <= 1.00005) { // Make sure we do not already have 1 (or something very close) in the list of roots. if ((foundRoots < 1 || !sk_doubles_nearly_equal_ulps(solution[0], 1)) && (foundRoots < 2 || !sk_doubles_nearly_equal_ulps(solution[1], 1))) { solution[foundRoots++] = 1; } } else if (tValue >= -0.00005 && (tValue <= 0.0 || sk_double_nearly_zero(tValue))) { // Make sure we do not already have 0 (or something very close) in the list of roots. if ((foundRoots < 1 || !sk_double_nearly_zero(solution[0])) && (foundRoots < 2 || !sk_double_nearly_zero(solution[1]))) { solution[foundRoots++] = 0; } } else if (tValue > 0.0 && tValue < 1.0) { solution[foundRoots++] = tValue; } } return foundRoots; } static bool skCubics_approximately_zero(double x) { // This cutoff for our binary search hopefully strikes a good balance between // performance and accuracy. return std::abs(x) < 0.00000001; } static int find_extrema_valid_t(double A, double B, double C, double t[2]) { // To find the local min and max of a cubic, we take the derivative and // solve when that is equal to 0. // d/dt (A*t^3 + B*t^2 + C*t + D) = 3A*t^2 + 2B*t + C double roots[2] = {0, 0}; int numRoots = SkQuads::RootsReal(3*A, 2*B, C, roots); int validRoots = 0; for (int i = 0; i < numRoots; i++) { double tValue = roots[i]; if (tValue >= 0 && tValue <= 1.0) { t[validRoots++] = tValue; } } return validRoots; } static double binary_search(double A, double B, double C, double D, double start, double stop) { SkASSERT(start <= stop); double left = SkCubics::EvalAt(A, B, C, D, start); if (skCubics_approximately_zero(left)) { return start; } double right = SkCubics::EvalAt(A, B, C, D, stop); if (!std::isfinite(left) || !std::isfinite(right)) { return -1; // Not going to deal with one or more endpoints being non-finite. } if ((left > 0 && right > 0) || (left < 0 && right < 0)) { return -1; // We can only have a root if one is above 0 and the other is below 0. } constexpr int maxIterations = 1000; // prevent infinite loop for (int i = 0; i < maxIterations; i++) { double step = (start + stop) / 2; double curr = SkCubics::EvalAt(A, B, C, D, step); if (skCubics_approximately_zero(curr)) { return step; } if ((curr < 0 && left < 0) || (curr > 0 && left > 0)) { // go right start = step; } else { // go left stop = step; } } return -1; } int SkCubics::BinarySearchRootsValidT(double A, double B, double C, double D, double solution[3]) { if (!std::isfinite(A) || !std::isfinite(B) || !std::isfinite(C) || !std::isfinite(D)) { return 0; } double regions[4] = {0, 0, 0, 1}; // Find local minima and maxima double minMax[2] = {0, 0}; int extremaCount = find_extrema_valid_t(A, B, C, minMax); int startIndex = 2 - extremaCount; if (extremaCount == 1) { regions[startIndex + 1] = minMax[0]; } if (extremaCount == 2) { // While the roots will be in the range 0 to 1 inclusive, they might not be sorted. regions[startIndex + 1] = std::min(minMax[0], minMax[1]); regions[startIndex + 2] = std::max(minMax[0], minMax[1]); } // Starting at regions[startIndex] and going up through regions[3], we have // an ascending list of numbers in the range 0 to 1.0, between which are the possible // locations of a root. int foundRoots = 0; for (;startIndex < 3; startIndex++) { double root = binary_search(A, B, C, D, regions[startIndex], regions[startIndex + 1]); if (root >= 0) { // Check for duplicates if ((foundRoots < 1 || !skCubics_approximately_zero(solution[0] - root)) && (foundRoots < 2 || !skCubics_approximately_zero(solution[1] - root))) { solution[foundRoots++] = root; } } } return foundRoots; } // Return the positive magnitude of a double. // * normalized - given 1.bbb...bbb x 2^e return 2^e. // * subnormal - return 0. // * nan & infinity - return infinity static double magnitude(double a) { static constexpr int64_t extractMagnitude = 0b0'11111111111'0000000000000000000000000000000000000000000000000000; int64_t bits; memcpy(&bits, &a, sizeof(bits)); bits &= extractMagnitude; double out; memcpy(&out, &bits, sizeof(out)); return out; } bool sk_doubles_nearly_equal_ulps(double a, double b, uint8_t maxUlpsDiff) { // The maximum magnitude to construct the ulp tolerance. The proper magnitude for // subnormal numbers is minMagnitude, which is 2^-1021, so if a and b are subnormal (having a // magnitude of 0) use minMagnitude. If a or b are infinity or nan, then maxMagnitude will be // +infinity. This means the tolerance will also be infinity, but the expression b - a below // will either be NaN or infinity, so a tolerance of infinity doesn't matter. static constexpr double minMagnitude = std::numeric_limits::min(); const double maxMagnitude = std::max(std::max(magnitude(a), minMagnitude), magnitude(b)); // Given a magnitude, this is the factor that generates the ulp for that magnitude. // In numbers, 2 ^ (-precision + 1) = 2 ^ -52. static constexpr double ulpFactor = std::numeric_limits::epsilon(); // The tolerance in ULPs given the maxMagnitude. Because the return statement must use < // for comparison instead of <= to correctly handle infinities, bump maxUlpsDiff up to get // the full maxUlpsDiff range. const double tolerance = maxMagnitude * (ulpFactor * (maxUlpsDiff + 1)); // The expression a == b is mainly for handling infinities, but it also catches the exact // equals. return a == b || std::abs(b - a) < tolerance; } bool sk_double_nearly_zero(double a) { return a == 0 || fabs(a) < std::numeric_limits::epsilon(); } // Copyright 2019 Google LLC. // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. void* sk_calloc_throw(size_t count, size_t elemSize) { return sk_calloc_throw(SkSafeMath::Mul(count, elemSize)); } void* sk_malloc_throw(size_t count, size_t elemSize) { return sk_malloc_throw(SkSafeMath::Mul(count, elemSize)); } void* sk_realloc_throw(void* buffer, size_t count, size_t elemSize) { return sk_realloc_throw(buffer, SkSafeMath::Mul(count, elemSize)); } void* sk_malloc_canfail(size_t count, size_t elemSize) { return sk_malloc_canfail(SkSafeMath::Mul(count, elemSize)); } // Solve 0 = M * x + B. If M is 0, there are no solutions, unless B is also 0, // in which case there are infinite solutions, so we just return 1 of them. static int solve_linear(const double M, const double B, double solution[2]) { if (sk_double_nearly_zero(M)) { solution[0] = 0; if (sk_double_nearly_zero(B)) { return 1; } return 0; } solution[0] = -B / M; if (!std::isfinite(solution[0])) { return 0; } return 1; } // When B >> A, then the x^2 component doesn't contribute much to the output, so the second root // will be very large, but have massive round off error. Because of the round off error, the // second root will not evaluate to zero when substituted back into the quadratic equation. In // the situation when B >> A, then just treat the quadratic as a linear equation. static bool close_to_linear(double A, double B) { if (A != 0) { // Return if B is much bigger than A. return std::abs(B / A) >= 1.0e+16; } // Otherwise A is zero, and the quadratic is linear. return true; } double SkQuads::Discriminant(const double a, const double b, const double c) { const double b2 = b * b; const double ac = a * c; // Calculate the rough discriminate which may suffer from a loss in precision due to b2 and // ac being too close. const double roughDiscriminant = b2 - ac; // We would like the calculated discriminant to have a relative error of 2-bits or less. For // doubles, this means the relative error is <= E = 3*2^-53. This gives a relative error // bounds of: // // |D - D~| / |D| <= E, // // where D = B*B - AC, and D~ is the floating point approximation of D. // Define the following equations // B2 = B*B, // B2~ = B2(1 + eB2), where eB2 is the floating point round off, // AC = A*C, // AC~ = AC(1 + eAC), where eAC is the floating point round off, and // D~ = B2~ - AC~. // We can now rewrite the above bounds as // // |B2 - AC - (B2~ - AC~)| / |B2 - AC| = |B2 - AC - B2~ + AC~| / |B2 - AC| <= E. // // Substituting B2~ and AC~, and canceling terms gives // // |eAC * AC - eB2 * B2| / |B2 - AC| <= max(|eAC|, |eBC|) * (|AC| + |B2|) / |B2 - AC|. // // We know that B2 is always positive, if AC is negative, then there is no cancellation // problem, and max(|eAC|, |eBC|) <= 2^-53, thus // // 2^-53 * (AC + B2) / |B2 - AC| <= 3 * 2^-53. Leading to // AC + B2 <= 3 * |B2 - AC|. // // If 3 * |B2 - AC| >= AC + B2 holds, then the roughDiscriminant has 2-bits of rounding error // or less and can be used. if (3 * std::abs(roughDiscriminant) >= b2 + ac) { return roughDiscriminant; } // Use the extra internal precision afforded by fma to calculate the rounding error for // b^2 and ac. const double b2RoundingError = std::fma(b, b, -b2); const double acRoundingError = std::fma(a, c, -ac); // Add the total rounding error back into the discriminant guess. const double discriminant = (b2 - ac) + (b2RoundingError - acRoundingError); return discriminant; } SkQuads::RootResult SkQuads::Roots(double A, double B, double C) { const double discriminant = Discriminant(A, B, C); if (A == 0) { double root; if (B == 0) { if (C == 0) { root = std::numeric_limits::infinity(); } else { root = std::numeric_limits::quiet_NaN(); } } else { // Solve -2*B*x + C == 0; x = c/(2*b). root = C / (2 * B); } return {discriminant, root, root}; } SkASSERT(A != 0); if (discriminant == 0) { return {discriminant, B / A, B / A}; } if (discriminant > 0) { const double D = sqrt(discriminant); const double R = B > 0 ? B + D : B - D; return {discriminant, R / A, C / R}; } // The discriminant is negative or is not finite. return {discriminant, NAN, NAN}; } static double zero_if_tiny(double x) { return sk_double_nearly_zero(x) ? 0 : x; } int SkQuads::RootsReal(const double A, const double B, const double C, double solution[2]) { if (close_to_linear(A, B)) { return solve_linear(B, C, solution); } SkASSERT(A != 0); auto [discriminant, root0, root1] = Roots(A, -0.5 * B, C); // Handle invariants to mesh with existing code from here on. if (!std::isfinite(discriminant) || discriminant < 0) { return 0; } int roots = 0; if (const double r0 = zero_if_tiny(root0); std::isfinite(r0)) { solution[roots++] = r0; } if (const double r1 = zero_if_tiny(root1); std::isfinite(r1)) { solution[roots++] = r1; } if (roots == 2 && sk_doubles_nearly_equal_ulps(solution[0], solution[1])) { roots = 1; } return roots; } double SkQuads::EvalAt(double A, double B, double C, double t) { // Use fused-multiply-add to reduce the amount of round-off error between terms. return std::fma(std::fma(A, t, B), t, C); } size_t SkSafeMath::Add(size_t x, size_t y) { SkSafeMath tmp; size_t sum = tmp.add(x, y); return tmp.ok() ? sum : SIZE_MAX; } size_t SkSafeMath::Mul(size_t x, size_t y) { SkSafeMath tmp; size_t prod = tmp.mul(x, y); return tmp.ok() ? prod : SIZE_MAX; } #if defined(SK_BUILD_FOR_MAC) || defined(SK_BUILD_FOR_IOS) struct SkSemaphore::OSSemaphore { dispatch_semaphore_t fSemaphore; OSSemaphore() { fSemaphore = dispatch_semaphore_create(0/*initial count*/); } ~OSSemaphore() { dispatch_release(fSemaphore); } void signal(int n) { while (n --> 0) { dispatch_semaphore_signal(fSemaphore); } } void wait() { dispatch_semaphore_wait(fSemaphore, DISPATCH_TIME_FOREVER); } }; #elif defined(SK_BUILD_FOR_WIN) struct SkSemaphore::OSSemaphore { HANDLE fSemaphore; OSSemaphore() { fSemaphore = CreateSemaphore(nullptr /*security attributes, optional*/, 0 /*initial count*/, MAXLONG /*max count*/, nullptr /*name, optional*/); } ~OSSemaphore() { CloseHandle(fSemaphore); } void signal(int n) { ReleaseSemaphore(fSemaphore, n, nullptr/*returns previous count, optional*/); } void wait() { WaitForSingleObject(fSemaphore, INFINITE/*timeout in ms*/); } }; #else // It's important we test for Mach before this. This code will compile but not work there. struct SkSemaphore::OSSemaphore { sem_t fSemaphore; OSSemaphore() { sem_init(&fSemaphore, 0/*cross process?*/, 0/*initial count*/); } ~OSSemaphore() { sem_destroy(&fSemaphore); } void signal(int n) { while (n --> 0) { sem_post(&fSemaphore); } } void wait() { // Try until we're not interrupted. while(sem_wait(&fSemaphore) == -1 && errno == EINTR); } }; #endif /////////////////////////////////////////////////////////////////////////////// SkSemaphore::~SkSemaphore() { delete fOSSemaphore; } void SkSemaphore::osSignal(int n) { fOSSemaphoreOnce([this] { fOSSemaphore = new OSSemaphore; }); fOSSemaphore->signal(n); } void SkSemaphore::osWait() { fOSSemaphoreOnce([this] { fOSSemaphore = new OSSemaphore; }); fOSSemaphore->wait(); } bool SkSemaphore::try_wait() { int count = fCount.load(std::memory_order_relaxed); if (count > 0) { return fCount.compare_exchange_weak(count, count-1, std::memory_order_acquire); } return false; } SkTDStorage::SkTDStorage(int sizeOfT) : fSizeOfT{sizeOfT} {} SkTDStorage::SkTDStorage(const void* src, int size, int sizeOfT) : fSizeOfT{sizeOfT} , fCapacity{size} , fSize{size} { if (size > 0) { SkASSERT(src != nullptr); size_t storageSize = this->bytes(size); fStorage = static_cast(sk_malloc_throw(storageSize)); memcpy(fStorage, src, storageSize); } } SkTDStorage::SkTDStorage(const SkTDStorage& that) : SkTDStorage{that.fStorage, that.fSize, that.fSizeOfT} {} SkTDStorage& SkTDStorage::operator=(const SkTDStorage& that) { if (this != &that) { if (that.fSize <= fCapacity) { fSize = that.fSize; if (fSize > 0) { memcpy(fStorage, that.data(), that.size_bytes()); } } else { *this = SkTDStorage{that.data(), that.size(), that.fSizeOfT}; } } return *this; } SkTDStorage::SkTDStorage(SkTDStorage&& that) : fSizeOfT{that.fSizeOfT} , fStorage(std::exchange(that.fStorage, nullptr)) , fCapacity{that.fCapacity} , fSize{that.fSize} {} SkTDStorage& SkTDStorage::operator=(SkTDStorage&& that) { if (this != &that) { this->~SkTDStorage(); new (this) SkTDStorage{std::move(that)}; } return *this; } SkTDStorage::~SkTDStorage() { sk_free(fStorage); } void SkTDStorage::reset() { const int sizeOfT = fSizeOfT; this->~SkTDStorage(); new (this) SkTDStorage{sizeOfT}; } void SkTDStorage::swap(SkTDStorage& that) { SkASSERT(fSizeOfT == that.fSizeOfT); using std::swap; swap(fStorage, that.fStorage); swap(fCapacity, that.fCapacity); swap(fSize, that.fSize); } void SkTDStorage::resize(int newSize) { SkASSERT(newSize >= 0); if (newSize > fCapacity) { this->reserve(newSize); } fSize = newSize; } void SkTDStorage::reserve(int newCapacity) { SkASSERT(newCapacity >= 0); if (newCapacity > fCapacity) { // Establish the maximum number of elements that includes a valid count for end. In the // largest case end() = &fArray[INT_MAX] which is 1 after the last indexable element. static constexpr int kMaxCount = INT_MAX; // Assume that the array will max out. int expandedReserve = kMaxCount; if (kMaxCount - newCapacity > 4) { // Add 1/4 more than we need. Add 4 to ensure this grows by at least 1. Pin to // kMaxCount if no room for 1/4 growth. int growth = 4 + ((newCapacity + 4) >> 2); // Read this line as: if (count + growth < kMaxCount) { ... } // It's rewritten to avoid signed integer overflow. if (kMaxCount - newCapacity > growth) { expandedReserve = newCapacity + growth; } } // With a T size of 1, the above allocator produces the progression of 7, 15, ... Since, // the sizeof max_align_t is often 16, there is no reason to allocate anything less than // 16 bytes. This eliminates a realloc when pushing back bytes to an SkTDArray. if (fSizeOfT == 1) { // Round up to the multiple of 16. expandedReserve = (expandedReserve + 15) & ~15; } fCapacity = expandedReserve; size_t newStorageSize = this->bytes(fCapacity); fStorage = static_cast(sk_realloc_throw(fStorage, newStorageSize)); } } void SkTDStorage::shrink_to_fit() { if (fCapacity != fSize) { fCapacity = fSize; // Because calling realloc with size of 0 is implementation defined, force to a good state // by freeing fStorage. if (fCapacity > 0) { fStorage = static_cast(sk_realloc_throw(fStorage, this->bytes(fCapacity))); } else { sk_free(fStorage); fStorage = nullptr; } } } void SkTDStorage::erase(int index, int count) { SkASSERT(count >= 0); SkASSERT(fSize >= count); SkASSERT(0 <= index && index <= fSize); if (count > 0) { // Check that the resulting size fits in an int. This will abort if not. const int newCount = this->calculateSizeOrDie(-count); this->moveTail(index, index + count, fSize); this->resize(newCount); } } void SkTDStorage::removeShuffle(int index) { SkASSERT(fSize > 0); SkASSERT(0 <= index && index < fSize); // Check that the new count is valid. const int newCount = this->calculateSizeOrDie(-1); this->moveTail(index, fSize - 1, fSize); this->resize(newCount); } void* SkTDStorage::prepend() { return this->insert(/*index=*/0); } void SkTDStorage::append() { if (fSize < fCapacity) { fSize++; } else { this->insert(fSize); } } void SkTDStorage::append(int count) { SkASSERT(count >= 0); // Read as: if (fSize + count <= fCapacity) {...}. This is a UB safe way to avoid the add. if (fCapacity - fSize >= count) { fSize += count; } else { this->insert(fSize, count, nullptr); } } void* SkTDStorage::append(const void* src, int count) { return this->insert(fSize, count, src); } void* SkTDStorage::insert(int index) { return this->insert(index, /*count=*/1, nullptr); } void* SkTDStorage::insert(int index, int count, const void* src) { SkASSERT(0 <= index && index <= fSize); SkASSERT(count >= 0); if (count > 0) { const int oldCount = fSize; const int newCount = this->calculateSizeOrDie(count); this->resize(newCount); this->moveTail(index + count, index, oldCount); if (src != nullptr) { this->copySrc(index, src, count); } } return this->address(index); } bool operator==(const SkTDStorage& a, const SkTDStorage& b) { return a.size() == b.size() && (a.size() == 0 || !memcmp(a.data(), b.data(), a.bytes(a.size()))); } int SkTDStorage::calculateSizeOrDie(int delta) { // Check that count will not go negative. SkASSERT_RELEASE(-fSize <= delta); // We take care to avoid overflow here. // Because count and delta are both signed 32-bit ints, the sum of count and delta is at // most 4294967294, which fits fine in uint32_t. Proof follows in assert. static_assert(UINT32_MAX >= (uint32_t)INT_MAX + (uint32_t)INT_MAX); uint32_t testCount = (uint32_t)fSize + (uint32_t)delta; SkASSERT_RELEASE(SkTFitsIn(testCount)); return SkToInt(testCount); } void SkTDStorage::moveTail(int to, int tailStart, int tailEnd) { SkASSERT(0 <= to && to <= fSize); SkASSERT(0 <= tailStart && tailStart <= tailEnd && tailEnd <= fSize); if (to != tailStart && tailStart != tailEnd) { this->copySrc(to, this->address(tailStart), tailEnd - tailStart); } } void SkTDStorage::copySrc(int dstIndex, const void* src, int count) { SkASSERT(count > 0); memmove(this->address(dstIndex), src, this->bytes(count)); } #ifdef SK_BUILD_FOR_WIN SkThreadID SkGetThreadID() { return GetCurrentThreadId(); } #else SkThreadID SkGetThreadID() { return (int64_t)pthread_self(); } #endif // Copyright 2018 Google LLC. // Use of this source code is governed by a BSD-style license that can be found in the LICENSE file. static constexpr inline int32_t left_shift(int32_t value, int32_t shift) { return (int32_t) ((uint32_t) value << shift); } template static constexpr bool is_align2(T x) { return 0 == (x & 1); } template static constexpr bool is_align4(T x) { return 0 == (x & 3); } static constexpr inline bool utf16_is_high_surrogate(uint16_t c) { return (c & 0xFC00) == 0xD800; } static constexpr inline bool utf16_is_low_surrogate(uint16_t c) { return (c & 0xFC00) == 0xDC00; } /** @returns -1 iff invalid UTF8 byte, 0 iff UTF8 continuation byte, 1 iff ASCII byte, 2 iff leading byte of 2-byte sequence, 3 iff leading byte of 3-byte sequence, and 4 iff leading byte of 4-byte sequence. I.e.: if return value > 0, then gives length of sequence. */ static int utf8_byte_type(uint8_t c) { if (c < 0x80) { return 1; } else if (c < 0xC0) { return 0; } else if (c >= 0xF5 || (c & 0xFE) == 0xC0) { // "octet values c0, c1, f5 to ff never appear" return -1; } else { int value = (((0xe5 << 24) >> ((unsigned)c >> 4 << 1)) & 3) + 1; // assert(value >= 2 && value <=4); return value; } } static bool utf8_type_is_valid_leading_byte(int type) { return type > 0; } static bool utf8_byte_is_continuation(uint8_t c) { return utf8_byte_type(c) == 0; } //////////////////////////////////////////////////////////////////////////////// int SkUTF::CountUTF8(const char* utf8, size_t byteLength) { if (!utf8 && byteLength) { return -1; } int count = 0; const char* stop = utf8 + byteLength; while (utf8 < stop) { int type = utf8_byte_type(*(const uint8_t*)utf8); if (!utf8_type_is_valid_leading_byte(type) || utf8 + type > stop) { return -1; // Sequence extends beyond end. } while(type-- > 1) { ++utf8; if (!utf8_byte_is_continuation(*(const uint8_t*)utf8)) { return -1; } } ++utf8; ++count; } return count; } int SkUTF::CountUTF16(const uint16_t* utf16, size_t byteLength) { if (!utf16 || !is_align2(intptr_t(utf16)) || !is_align2(byteLength)) { return -1; } const uint16_t* src = (const uint16_t*)utf16; const uint16_t* stop = src + (byteLength >> 1); int count = 0; while (src < stop) { unsigned c = *src++; if (utf16_is_low_surrogate(c)) { return -1; } if (utf16_is_high_surrogate(c)) { if (src >= stop) { return -1; } c = *src++; if (!utf16_is_low_surrogate(c)) { return -1; } } count += 1; } return count; } int SkUTF::CountUTF32(const int32_t* utf32, size_t byteLength) { if (!is_align4(intptr_t(utf32)) || !is_align4(byteLength) || !SkTFitsIn(byteLength >> 2)) { return -1; } const uint32_t kInvalidUnicharMask = 0xFF000000; // unichar fits in 24 bits const uint32_t* ptr = (const uint32_t*)utf32; const uint32_t* stop = ptr + (byteLength >> 2); while (ptr < stop) { if (*ptr & kInvalidUnicharMask) { return -1; } ptr += 1; } return (int)(byteLength >> 2); } template static SkUnichar next_fail(const T** ptr, const T* end) { *ptr = end; return -1; } SkUnichar SkUTF::NextUTF8(const char** ptr, const char* end) { if (!ptr || !end ) { return -1; } const uint8_t* p = (const uint8_t*)*ptr; if (!p || p >= (const uint8_t*)end) { return next_fail(ptr, end); } int c = *p; int hic = c << 24; if (!utf8_type_is_valid_leading_byte(utf8_byte_type(c))) { return next_fail(ptr, end); } if (hic < 0) { uint32_t mask = (uint32_t)~0x3F; hic = left_shift(hic, 1); do { ++p; if (p >= (const uint8_t*)end) { return next_fail(ptr, end); } // check before reading off end of array. uint8_t nextByte = *p; if (!utf8_byte_is_continuation(nextByte)) { return next_fail(ptr, end); } c = (c << 6) | (nextByte & 0x3F); mask <<= 5; } while ((hic = left_shift(hic, 1)) < 0); c &= ~mask; } *ptr = (char*)p + 1; return c; } SkUnichar SkUTF::NextUTF16(const uint16_t** ptr, const uint16_t* end) { if (!ptr || !end ) { return -1; } const uint16_t* src = *ptr; if (!src || src + 1 > end || !is_align2(intptr_t(src))) { return next_fail(ptr, end); } uint16_t c = *src++; SkUnichar result = c; if (utf16_is_low_surrogate(c)) { return next_fail(ptr, end); // srcPtr should never point at low surrogate. } if (utf16_is_high_surrogate(c)) { if (src + 1 > end) { return next_fail(ptr, end); // Truncated string. } uint16_t low = *src++; if (!utf16_is_low_surrogate(low)) { return next_fail(ptr, end); } /* [paraphrased from wikipedia] Take the high surrogate and subtract 0xD800, then multiply by 0x400. Take the low surrogate and subtract 0xDC00. Add these two results together, and finally add 0x10000 to get the final decoded codepoint. unicode = (high - 0xD800) * 0x400 + low - 0xDC00 + 0x10000 unicode = (high * 0x400) - (0xD800 * 0x400) + low - 0xDC00 + 0x10000 unicode = (high << 10) - (0xD800 << 10) + low - 0xDC00 + 0x10000 unicode = (high << 10) + low - ((0xD800 << 10) + 0xDC00 - 0x10000) */ result = (result << 10) + (SkUnichar)low - ((0xD800 << 10) + 0xDC00 - 0x10000); } *ptr = src; return result; } SkUnichar SkUTF::NextUTF32(const int32_t** ptr, const int32_t* end) { if (!ptr || !end ) { return -1; } const int32_t* s = *ptr; if (!s || s + 1 > end || !is_align4(intptr_t(s))) { return next_fail(ptr, end); } int32_t value = *s; const uint32_t kInvalidUnicharMask = 0xFF000000; // unichar fits in 24 bits if (value & kInvalidUnicharMask) { return next_fail(ptr, end); } *ptr = s + 1; return value; } size_t SkUTF::ToUTF8(SkUnichar uni, char utf8[SkUTF::kMaxBytesInUTF8Sequence]) { if ((uint32_t)uni > 0x10FFFF) { return 0; } if (uni <= 127) { if (utf8) { *utf8 = (char)uni; } return 1; } char tmp[4]; char* p = tmp; size_t count = 1; while (uni > 0x7F >> count) { *p++ = (char)(0x80 | (uni & 0x3F)); uni >>= 6; count += 1; } if (utf8) { p = tmp; utf8 += count; while (p < tmp + count - 1) { *--utf8 = *p++; } *--utf8 = (char)(~(0xFF >> count) | uni); } return count; } size_t SkUTF::ToUTF16(SkUnichar uni, uint16_t utf16[2]) { if ((uint32_t)uni > 0x10FFFF) { return 0; } int extra = (uni > 0xFFFF); if (utf16) { if (extra) { utf16[0] = (uint16_t)((0xD800 - 64) + (uni >> 10)); utf16[1] = (uint16_t)(0xDC00 | (uni & 0x3FF)); } else { utf16[0] = (uint16_t)uni; } } return 1 + extra; } int SkUTF::UTF8ToUTF16(uint16_t dst[], int dstCapacity, const char src[], size_t srcByteLength) { if (!dst) { dstCapacity = 0; } int dstLength = 0; uint16_t* endDst = dst + dstCapacity; const char* endSrc = src + srcByteLength; while (src < endSrc) { SkUnichar uni = NextUTF8(&src, endSrc); if (uni < 0) { return -1; } uint16_t utf16[2]; size_t count = ToUTF16(uni, utf16); if (count == 0) { return -1; } dstLength += count; if (dst) { uint16_t* elems = utf16; while (dst < endDst && count > 0) { *dst++ = *elems++; count -= 1; } } } return dstLength; } int SkUTF::UTF16ToUTF8(char dst[], int dstCapacity, const uint16_t src[], size_t srcLength) { if (!dst) { dstCapacity = 0; } int dstLength = 0; const char* endDst = dst + dstCapacity; const uint16_t* endSrc = src + srcLength; while (src < endSrc) { SkUnichar uni = NextUTF16(&src, endSrc); if (uni < 0) { return -1; } char utf8[SkUTF::kMaxBytesInUTF8Sequence]; size_t count = ToUTF8(uni, utf8); if (count == 0) { return -1; } dstLength += count; if (dst) { const char* elems = utf8; while (dst < endDst && count > 0) { *dst++ = *elems++; count -= 1; } } } return dstLength; } const char SkHexadecimalDigits::gUpper[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; const char SkHexadecimalDigits::gLower[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; SkCubicClipper::SkCubicClipper() { fClip.setEmpty(); } void SkCubicClipper::setClip(const SkIRect& clip) { // conver to scalars, since that's where we'll see the points fClip.set(clip); } bool SkCubicClipper::ChopMonoAtY(const SkPoint pts[4], SkScalar y, SkScalar* t) { SkScalar ycrv[4]; ycrv[0] = pts[0].fY - y; ycrv[1] = pts[1].fY - y; ycrv[2] = pts[2].fY - y; ycrv[3] = pts[3].fY - y; #ifdef NEWTON_RAPHSON // Quadratic convergence, typically <= 3 iterations. // Initial guess. // TODO(turk): Check for zero denominator? Shouldn't happen unless the curve // is not only monotonic but degenerate. SkScalar t1 = ycrv[0] / (ycrv[0] - ycrv[3]); // Newton's iterations. const SkScalar tol = SK_Scalar1 / 16384; // This leaves 2 fixed noise bits. SkScalar t0; const int maxiters = 5; int iters = 0; bool converged; do { t0 = t1; SkScalar y01 = SkScalarInterp(ycrv[0], ycrv[1], t0); SkScalar y12 = SkScalarInterp(ycrv[1], ycrv[2], t0); SkScalar y23 = SkScalarInterp(ycrv[2], ycrv[3], t0); SkScalar y012 = SkScalarInterp(y01, y12, t0); SkScalar y123 = SkScalarInterp(y12, y23, t0); SkScalar y0123 = SkScalarInterp(y012, y123, t0); SkScalar yder = (y123 - y012) * 3; // TODO(turk): check for yder==0: horizontal. t1 -= y0123 / yder; converged = SkScalarAbs(t1 - t0) <= tol; // NaN-safe ++iters; } while (!converged && (iters < maxiters)); *t = t1; // Return the result. // The result might be valid, even if outside of the range [0, 1], but // we never evaluate a Bezier outside this interval, so we return false. if (t1 < 0 || t1 > SK_Scalar1) return false; // This shouldn't happen, but check anyway. return converged; #else // BISECTION // Linear convergence, typically 16 iterations. // Check that the endpoints straddle zero. SkScalar tNeg, tPos; // Negative and positive function parameters. if (ycrv[0] < 0) { if (ycrv[3] < 0) return false; tNeg = 0; tPos = SK_Scalar1; } else if (ycrv[0] > 0) { if (ycrv[3] > 0) return false; tNeg = SK_Scalar1; tPos = 0; } else { *t = 0; return true; } const SkScalar tol = SK_Scalar1 / 65536; // 1 for fixed, 1e-5 for float. do { SkScalar tMid = (tPos + tNeg) / 2; SkScalar y01 = SkScalarInterp(ycrv[0], ycrv[1], tMid); SkScalar y12 = SkScalarInterp(ycrv[1], ycrv[2], tMid); SkScalar y23 = SkScalarInterp(ycrv[2], ycrv[3], tMid); SkScalar y012 = SkScalarInterp(y01, y12, tMid); SkScalar y123 = SkScalarInterp(y12, y23, tMid); SkScalar y0123 = SkScalarInterp(y012, y123, tMid); if (y0123 == 0) { *t = tMid; return true; } if (y0123 < 0) tNeg = tMid; else tPos = tMid; } while (!(SkScalarAbs(tPos - tNeg) <= tol)); // Nan-safe *t = (tNeg + tPos) / 2; return true; #endif // BISECTION } bool SkCubicClipper::clipCubic(const SkPoint srcPts[4], SkPoint dst[4]) { bool reverse; // we need the data to be monotonically descending in Y if (srcPts[0].fY > srcPts[3].fY) { dst[0] = srcPts[3]; dst[1] = srcPts[2]; dst[2] = srcPts[1]; dst[3] = srcPts[0]; reverse = true; } else { memcpy(dst, srcPts, 4 * sizeof(SkPoint)); reverse = false; } // are we completely above or below const SkScalar ctop = fClip.fTop; const SkScalar cbot = fClip.fBottom; if (dst[3].fY <= ctop || dst[0].fY >= cbot) { return false; } SkScalar t; SkPoint tmp[7]; // for SkChopCubicAt // are we partially above if (dst[0].fY < ctop && ChopMonoAtY(dst, ctop, &t)) { SkChopCubicAt(dst, tmp, t); dst[0] = tmp[3]; dst[1] = tmp[4]; dst[2] = tmp[5]; } // are we partially below if (dst[3].fY > cbot && ChopMonoAtY(dst, cbot, &t)) { SkChopCubicAt(dst, tmp, t); dst[1] = tmp[1]; dst[2] = tmp[2]; dst[3] = tmp[3]; } if (reverse) { using std::swap; swap(dst[0], dst[3]); swap(dst[1], dst[2]); } return true; } SkData::SkData(const void* ptr, size_t size, ReleaseProc proc, void* context) : fReleaseProc(proc) , fReleaseProcContext(context) , fPtr(ptr) , fSize(size) {} /** This constructor means we are inline with our fPtr's contents. * Thus we set fPtr to point right after this. */ SkData::SkData(size_t size) : fReleaseProc(nullptr) , fReleaseProcContext(nullptr) , fPtr((const char*)(this + 1)) , fSize(size) {} SkData::~SkData() { if (fReleaseProc) { fReleaseProc(fPtr, fReleaseProcContext); } } bool SkData::equals(const SkData* other) const { if (this == other) { return true; } if (nullptr == other) { return false; } return fSize == other->fSize && !sk_careful_memcmp(fPtr, other->fPtr, fSize); } size_t SkData::copyRange(size_t offset, size_t length, void* buffer) const { size_t available = fSize; if (offset >= available || 0 == length) { return 0; } available -= offset; if (length > available) { length = available; } SkASSERT(length > 0); if (buffer) { memcpy(buffer, this->bytes() + offset, length); } return length; } void SkData::operator delete(void* p) { ::operator delete(p); } sk_sp SkData::PrivateNewWithCopy(const void* srcOrNull, size_t length) { if (0 == length) { return SkData::MakeEmpty(); } const size_t actualLength = length + sizeof(SkData); SkASSERT_RELEASE(length < actualLength); // Check for overflow. void* storage = ::operator new (actualLength); sk_sp data(new (storage) SkData(length)); if (srcOrNull) { memcpy(data->writable_data(), srcOrNull, length); } return data; } void SkData::NoopReleaseProc(const void*, void*) {} /////////////////////////////////////////////////////////////////////////////// sk_sp SkData::MakeEmpty() { static SkOnce once; static SkData* empty; once([]{ empty = new SkData(nullptr, 0, nullptr, nullptr); }); return sk_ref_sp(empty); } // assumes fPtr was allocated via sk_malloc static void sk_free_releaseproc(const void* ptr, void*) { sk_free((void*)ptr); } sk_sp SkData::MakeFromMalloc(const void* data, size_t length) { return sk_sp(new SkData(data, length, sk_free_releaseproc, nullptr)); } sk_sp SkData::MakeWithCopy(const void* src, size_t length) { SkASSERT(src); return PrivateNewWithCopy(src, length); } sk_sp SkData::MakeUninitialized(size_t length) { return PrivateNewWithCopy(nullptr, length); } sk_sp SkData::MakeZeroInitialized(size_t length) { auto data = MakeUninitialized(length); if (length != 0) { memset(data->writable_data(), 0, data->size()); } return data; } sk_sp SkData::MakeWithProc(const void* ptr, size_t length, ReleaseProc proc, void* ctx) { return sk_sp(new SkData(ptr, length, proc, ctx)); } // assumes context is a SkData static void sk_dataref_releaseproc(const void*, void* context) { SkData* src = reinterpret_cast(context); src->unref(); } sk_sp SkData::MakeSubset(const SkData* src, size_t offset, size_t length) { /* We could, if we wanted/need to, just make a deep copy of src's data, rather than referencing it. This would duplicate the storage (of the subset amount) but would possibly allow src to go out of scope sooner. */ size_t available = src->size(); if (offset >= available || 0 == length) { return SkData::MakeEmpty(); } available -= offset; if (length > available) { length = available; } SkASSERT(length > 0); src->ref(); // this will be balanced in sk_dataref_releaseproc return sk_sp(new SkData(src->bytes() + offset, length, sk_dataref_releaseproc, const_cast(src))); } sk_sp SkData::MakeWithCString(const char cstr[]) { size_t size; if (nullptr == cstr) { cstr = ""; size = 1; } else { size = strlen(cstr) + 1; } return MakeWithCopy(cstr, size); } /////////////////////////////////////////////////////////////////////////////// sk_sp SkData::MakeFromStream(SkStream* stream, size_t size) { // reduce the chance of OOM by checking that the stream has enough bytes to read from before // allocating that potentially large buffer. if (StreamRemainingLengthIsBelow(stream, size)) { return nullptr; } sk_sp data(SkData::MakeUninitialized(size)); if (stream->read(data->writable_data(), size) != size) { return nullptr; } return data; } static bool quick_reject(const SkRect& bounds, const SkRect& clip) { return bounds.fTop >= clip.fBottom || bounds.fBottom <= clip.fTop; } static inline void clamp_le(SkScalar& value, SkScalar max) { if (value > max) { value = max; } } static inline void clamp_ge(SkScalar& value, SkScalar min) { if (value < min) { value = min; } } /* src[] must be monotonic in Y. This routine copies src into dst, and sorts it to be increasing in Y. If it had to reverse the order of the points, it returns true, otherwise it returns false */ static bool sort_increasing_Y(SkPoint dst[], const SkPoint src[], int count) { // we need the data to be monotonically increasing in Y if (src[0].fY > src[count - 1].fY) { for (int i = 0; i < count; i++) { dst[i] = src[count - i - 1]; } return true; } else { memcpy(dst, src, count * sizeof(SkPoint)); return false; } } bool SkEdgeClipper::clipLine(SkPoint p0, SkPoint p1, const SkRect& clip) { fCurrPoint = fPoints; fCurrVerb = fVerbs; SkPoint lines[SkLineClipper::kMaxPoints]; const SkPoint pts[] = { p0, p1 }; int lineCount = SkLineClipper::ClipLine(pts, clip, lines, fCanCullToTheRight); for (int i = 0; i < lineCount; i++) { this->appendLine(lines[i], lines[i + 1]); } *fCurrVerb = SkPath::kDone_Verb; fCurrPoint = fPoints; fCurrVerb = fVerbs; return SkPath::kDone_Verb != fVerbs[0]; } /////////////////////////////////////////////////////////////////////////////// static bool chopMonoQuadAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar target, SkScalar* t) { /* Solve F(t) = y where F(t) := [0](1-t)^2 + 2[1]t(1-t) + [2]t^2 * We solve for t, using quadratic equation, hence we have to rearrange * our cooefficents to look like At^2 + Bt + C */ SkScalar A = c0 - c1 - c1 + c2; SkScalar B = 2*(c1 - c0); SkScalar C = c0 - target; SkScalar roots[2]; // we only expect one, but make room for 2 for safety int count = SkFindUnitQuadRoots(A, B, C, roots); if (count) { *t = roots[0]; return true; } return false; } static bool chopMonoQuadAtY(SkPoint pts[3], SkScalar y, SkScalar* t) { return chopMonoQuadAt(pts[0].fY, pts[1].fY, pts[2].fY, y, t); } static bool chopMonoQuadAtX(SkPoint pts[3], SkScalar x, SkScalar* t) { return chopMonoQuadAt(pts[0].fX, pts[1].fX, pts[2].fX, x, t); } // Modify pts[] in place so that it is clipped in Y to the clip rect static void chop_quad_in_Y(SkPoint pts[3], const SkRect& clip) { SkScalar t; SkPoint tmp[5]; // for SkChopQuadAt // are we partially above if (pts[0].fY < clip.fTop) { if (chopMonoQuadAtY(pts, clip.fTop, &t)) { // take the 2nd chopped quad SkChopQuadAt(pts, tmp, t); // clamp to clean up imprecise numerics in the chop tmp[2].fY = clip.fTop; clamp_ge(tmp[3].fY, clip.fTop); pts[0] = tmp[2]; pts[1] = tmp[3]; } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the top for (int i = 0; i < 3; i++) { if (pts[i].fY < clip.fTop) { pts[i].fY = clip.fTop; } } } } // are we partially below if (pts[2].fY > clip.fBottom) { if (chopMonoQuadAtY(pts, clip.fBottom, &t)) { SkChopQuadAt(pts, tmp, t); // clamp to clean up imprecise numerics in the chop clamp_le(tmp[1].fY, clip.fBottom); tmp[2].fY = clip.fBottom; pts[1] = tmp[1]; pts[2] = tmp[2]; } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the bottom for (int i = 0; i < 3; i++) { if (pts[i].fY > clip.fBottom) { pts[i].fY = clip.fBottom; } } } } } // srcPts[] must be monotonic in X and Y void SkEdgeClipper::clipMonoQuad(const SkPoint srcPts[3], const SkRect& clip) { SkPoint pts[3]; bool reverse = sort_increasing_Y(pts, srcPts, 3); // are we completely above or below if (pts[2].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { return; } // Now chop so that pts is contained within clip in Y chop_quad_in_Y(pts, clip); if (pts[0].fX > pts[2].fX) { using std::swap; swap(pts[0], pts[2]); reverse = !reverse; } SkASSERT(pts[0].fX <= pts[1].fX); SkASSERT(pts[1].fX <= pts[2].fX); // Now chop in X has needed, and record the segments if (pts[2].fX <= clip.fLeft) { // wholly to the left this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); return; } if (pts[0].fX >= clip.fRight) { // wholly to the right if (!this->canCullToTheRight()) { this->appendVLine(clip.fRight, pts[0].fY, pts[2].fY, reverse); } return; } SkScalar t; SkPoint tmp[5]; // for SkChopQuadAt // are we partially to the left if (pts[0].fX < clip.fLeft) { if (chopMonoQuadAtX(pts, clip.fLeft, &t)) { SkChopQuadAt(pts, tmp, t); this->appendVLine(clip.fLeft, tmp[0].fY, tmp[2].fY, reverse); // clamp to clean up imprecise numerics in the chop tmp[2].fX = clip.fLeft; clamp_ge(tmp[3].fX, clip.fLeft); pts[0] = tmp[2]; pts[1] = tmp[3]; } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the left this->appendVLine(clip.fLeft, pts[0].fY, pts[2].fY, reverse); return; } } // are we partially to the right if (pts[2].fX > clip.fRight) { if (chopMonoQuadAtX(pts, clip.fRight, &t)) { SkChopQuadAt(pts, tmp, t); // clamp to clean up imprecise numerics in the chop clamp_le(tmp[1].fX, clip.fRight); tmp[2].fX = clip.fRight; this->appendQuad(tmp, reverse); this->appendVLine(clip.fRight, tmp[2].fY, tmp[4].fY, reverse); } else { // if chopMonoQuadAtY failed, then we may have hit inexact numerics // so we just clamp against the right pts[1].fX = std::min(pts[1].fX, clip.fRight); pts[2].fX = std::min(pts[2].fX, clip.fRight); this->appendQuad(pts, reverse); } } else { // wholly inside the clip this->appendQuad(pts, reverse); } } bool SkEdgeClipper::clipQuad(const SkPoint srcPts[3], const SkRect& clip) { fCurrPoint = fPoints; fCurrVerb = fVerbs; SkRect bounds; bounds.setBounds(srcPts, 3); if (!quick_reject(bounds, clip)) { SkPoint monoY[5]; int countY = SkChopQuadAtYExtrema(srcPts, monoY); for (int y = 0; y <= countY; y++) { SkPoint monoX[5]; int countX = SkChopQuadAtXExtrema(&monoY[y * 2], monoX); for (int x = 0; x <= countX; x++) { this->clipMonoQuad(&monoX[x * 2], clip); SkASSERT(fCurrVerb - fVerbs < kMaxVerbs); SkASSERT(fCurrPoint - fPoints <= kMaxPoints); } } } *fCurrVerb = SkPath::kDone_Verb; fCurrPoint = fPoints; fCurrVerb = fVerbs; return SkPath::kDone_Verb != fVerbs[0]; } /////////////////////////////////////////////////////////////////////////////// static SkScalar mono_cubic_closestT(const SkScalar src[], SkScalar x) { SkScalar t = 0.5f; SkScalar lastT; SkScalar bestT SK_INIT_TO_AVOID_WARNING; SkScalar step = 0.25f; SkScalar D = src[0]; SkScalar A = src[6] + 3*(src[2] - src[4]) - D; SkScalar B = 3*(src[4] - src[2] - src[2] + D); SkScalar C = 3*(src[2] - D); x -= D; SkScalar closest = SK_ScalarMax; do { SkScalar loc = ((A * t + B) * t + C) * t; SkScalar dist = SkScalarAbs(loc - x); if (closest > dist) { closest = dist; bestT = t; } lastT = t; t += loc < x ? step : -step; step *= 0.5f; } while (closest > 0.25f && lastT != t); return bestT; } static void chop_mono_cubic_at_y(SkPoint src[4], SkScalar y, SkPoint dst[7]) { if (SkChopMonoCubicAtY(src, y, dst)) { return; } SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fY, y)); } // Modify pts[] in place so that it is clipped in Y to the clip rect static void chop_cubic_in_Y(SkPoint pts[4], const SkRect& clip) { // are we partially above if (pts[0].fY < clip.fTop) { SkPoint tmp[7]; chop_mono_cubic_at_y(pts, clip.fTop, tmp); /* * For a large range in the points, we can do a poor job of chopping, such that the t * we computed resulted in the lower cubic still being partly above the clip. * * If just the first or first 2 Y values are above the fTop, we can just smash them * down. If the first 3 Ys are above fTop, we can't smash all 3, as that can really * distort the cubic. In this case, we take the first output (tmp[3..6] and treat it as * a guess, and re-chop against fTop. Then we fall through to checking if we need to * smash the first 1 or 2 Y values. */ if (tmp[3].fY < clip.fTop && tmp[4].fY < clip.fTop && tmp[5].fY < clip.fTop) { SkPoint tmp2[4]; memcpy(tmp2, &tmp[3].fX, 4 * sizeof(SkPoint)); chop_mono_cubic_at_y(tmp2, clip.fTop, tmp); } // tmp[3, 4].fY should all be to the below clip.fTop. // Since we can't trust the numerics of the chopper, we force those conditions now tmp[3].fY = clip.fTop; clamp_ge(tmp[4].fY, clip.fTop); pts[0] = tmp[3]; pts[1] = tmp[4]; pts[2] = tmp[5]; } // are we partially below if (pts[3].fY > clip.fBottom) { SkPoint tmp[7]; chop_mono_cubic_at_y(pts, clip.fBottom, tmp); tmp[3].fY = clip.fBottom; clamp_le(tmp[2].fY, clip.fBottom); pts[1] = tmp[1]; pts[2] = tmp[2]; pts[3] = tmp[3]; } } static void chop_mono_cubic_at_x(SkPoint src[4], SkScalar x, SkPoint dst[7]) { if (SkChopMonoCubicAtX(src, x, dst)) { return; } SkChopCubicAt(src, dst, mono_cubic_closestT(&src->fX, x)); } // srcPts[] must be monotonic in X and Y void SkEdgeClipper::clipMonoCubic(const SkPoint src[4], const SkRect& clip) { SkPoint pts[4]; bool reverse = sort_increasing_Y(pts, src, 4); // are we completely above or below if (pts[3].fY <= clip.fTop || pts[0].fY >= clip.fBottom) { return; } // Now chop so that pts is contained within clip in Y chop_cubic_in_Y(pts, clip); if (pts[0].fX > pts[3].fX) { using std::swap; swap(pts[0], pts[3]); swap(pts[1], pts[2]); reverse = !reverse; } // Now chop in X has needed, and record the segments if (pts[3].fX <= clip.fLeft) { // wholly to the left this->appendVLine(clip.fLeft, pts[0].fY, pts[3].fY, reverse); return; } if (pts[0].fX >= clip.fRight) { // wholly to the right if (!this->canCullToTheRight()) { this->appendVLine(clip.fRight, pts[0].fY, pts[3].fY, reverse); } return; } // are we partially to the left if (pts[0].fX < clip.fLeft) { SkPoint tmp[7]; chop_mono_cubic_at_x(pts, clip.fLeft, tmp); this->appendVLine(clip.fLeft, tmp[0].fY, tmp[3].fY, reverse); // tmp[3, 4].fX should all be to the right of clip.fLeft. // Since we can't trust the numerics of // the chopper, we force those conditions now tmp[3].fX = clip.fLeft; clamp_ge(tmp[4].fX, clip.fLeft); pts[0] = tmp[3]; pts[1] = tmp[4]; pts[2] = tmp[5]; } // are we partially to the right if (pts[3].fX > clip.fRight) { SkPoint tmp[7]; chop_mono_cubic_at_x(pts, clip.fRight, tmp); tmp[3].fX = clip.fRight; clamp_le(tmp[2].fX, clip.fRight); this->appendCubic(tmp, reverse); this->appendVLine(clip.fRight, tmp[3].fY, tmp[6].fY, reverse); } else { // wholly inside the clip this->appendCubic(pts, reverse); } } static SkRect compute_cubic_bounds(const SkPoint pts[4]) { SkRect r; r.setBounds(pts, 4); return r; } static bool too_big_for_reliable_float_math(const SkRect& r) { // limit set as the largest float value for which we can still reliably compute things like // - chopping at XY extrema // - chopping at Y or X values for clipping // // Current value chosen just by experiment. Larger (and still succeeds) is always better. // const SkScalar limit = 1 << 22; return r.fLeft < -limit || r.fTop < -limit || r.fRight > limit || r.fBottom > limit; } bool SkEdgeClipper::clipCubic(const SkPoint srcPts[4], const SkRect& clip) { fCurrPoint = fPoints; fCurrVerb = fVerbs; const SkRect bounds = compute_cubic_bounds(srcPts); // check if we're clipped out vertically if (bounds.fBottom > clip.fTop && bounds.fTop < clip.fBottom) { if (too_big_for_reliable_float_math(bounds)) { // can't safely clip the cubic, so we give up and draw a line (which we can safely clip) // // If we rewrote chopcubicat*extrema and chopmonocubic using doubles, we could very // likely always handle the cubic safely, but (it seems) at a big loss in speed, so // we'd only want to take that alternate impl if needed. Perhaps a TODO to try it. // return this->clipLine(srcPts[0], srcPts[3], clip); } else { SkPoint monoY[10]; int countY = SkChopCubicAtYExtrema(srcPts, monoY); for (int y = 0; y <= countY; y++) { SkPoint monoX[10]; int countX = SkChopCubicAtXExtrema(&monoY[y * 3], monoX); for (int x = 0; x <= countX; x++) { this->clipMonoCubic(&monoX[x * 3], clip); SkASSERT(fCurrVerb - fVerbs < kMaxVerbs); SkASSERT(fCurrPoint - fPoints <= kMaxPoints); } } } } *fCurrVerb = SkPath::kDone_Verb; fCurrPoint = fPoints; fCurrVerb = fVerbs; return SkPath::kDone_Verb != fVerbs[0]; } /////////////////////////////////////////////////////////////////////////////// void SkEdgeClipper::appendLine(SkPoint p0, SkPoint p1) { *fCurrVerb++ = SkPath::kLine_Verb; fCurrPoint[0] = p0; fCurrPoint[1] = p1; fCurrPoint += 2; } void SkEdgeClipper::appendVLine(SkScalar x, SkScalar y0, SkScalar y1, bool reverse) { *fCurrVerb++ = SkPath::kLine_Verb; if (reverse) { using std::swap; swap(y0, y1); } fCurrPoint[0].set(x, y0); fCurrPoint[1].set(x, y1); fCurrPoint += 2; } void SkEdgeClipper::appendQuad(const SkPoint pts[3], bool reverse) { *fCurrVerb++ = SkPath::kQuad_Verb; if (reverse) { fCurrPoint[0] = pts[2]; fCurrPoint[2] = pts[0]; } else { fCurrPoint[0] = pts[0]; fCurrPoint[2] = pts[2]; } fCurrPoint[1] = pts[1]; fCurrPoint += 3; } void SkEdgeClipper::appendCubic(const SkPoint pts[4], bool reverse) { *fCurrVerb++ = SkPath::kCubic_Verb; if (reverse) { for (int i = 0; i < 4; i++) { fCurrPoint[i] = pts[3 - i]; } } else { memcpy(fCurrPoint, pts, 4 * sizeof(SkPoint)); } fCurrPoint += 4; } SkPath::Verb SkEdgeClipper::next(SkPoint pts[]) { SkPath::Verb verb = *fCurrVerb; switch (verb) { case SkPath::kLine_Verb: memcpy(pts, fCurrPoint, 2 * sizeof(SkPoint)); fCurrPoint += 2; fCurrVerb += 1; break; case SkPath::kQuad_Verb: memcpy(pts, fCurrPoint, 3 * sizeof(SkPoint)); fCurrPoint += 3; fCurrVerb += 1; break; case SkPath::kCubic_Verb: memcpy(pts, fCurrPoint, 4 * sizeof(SkPoint)); fCurrPoint += 4; fCurrVerb += 1; break; case SkPath::kDone_Verb: break; default: SkDEBUGFAIL("unexpected verb in quadclippper2 iter"); break; } return verb; } /////////////////////////////////////////////////////////////////////////////// #ifdef SK_DEBUG static void assert_monotonic(const SkScalar coord[], int count) { if (coord[0] > coord[(count - 1) * 2]) { for (int i = 1; i < count; i++) { SkASSERT(coord[2 * (i - 1)] >= coord[i * 2]); } } else if (coord[0] < coord[(count - 1) * 2]) { for (int i = 1; i < count; i++) { SkASSERT(coord[2 * (i - 1)] <= coord[i * 2]); } } else { for (int i = 1; i < count; i++) { SkASSERT(coord[2 * (i - 1)] == coord[i * 2]); } } } void sk_assert_monotonic_y(const SkPoint pts[], int count) { if (count > 1) { assert_monotonic(&pts[0].fY, count); } } void sk_assert_monotonic_x(const SkPoint pts[], int count) { if (count > 1) { assert_monotonic(&pts[0].fX, count); } } #endif void SkEdgeClipper::ClipPath(const SkPath& path, const SkRect& clip, bool canCullToTheRight, void (*consume)(SkEdgeClipper*, bool newCtr, void* ctx), void* ctx) { SkASSERT(path.isFinite()); SkAutoConicToQuads quadder; const SkScalar conicTol = SK_Scalar1 / 4; SkPathEdgeIter iter(path); SkEdgeClipper clipper(canCullToTheRight); while (auto e = iter.next()) { switch (e.fEdge) { case SkPathEdgeIter::Edge::kLine: if (clipper.clipLine(e.fPts[0], e.fPts[1], clip)) { consume(&clipper, e.fIsNewContour, ctx); } break; case SkPathEdgeIter::Edge::kQuad: if (clipper.clipQuad(e.fPts, clip)) { consume(&clipper, e.fIsNewContour, ctx); } break; case SkPathEdgeIter::Edge::kConic: { const SkPoint* quadPts = quadder.computeQuads(e.fPts, iter.conicWeight(), conicTol); for (int i = 0; i < quadder.countQuads(); ++i) { if (clipper.clipQuad(quadPts, clip)) { consume(&clipper, e.fIsNewContour, ctx); } quadPts += 2; } } break; case SkPathEdgeIter::Edge::kCubic: if (clipper.clipCubic(e.fPts, clip)) { consume(&clipper, e.fIsNewContour, ctx); } break; } } } namespace { using float2 = skvx::float2; using float4 = skvx::float4; SkVector to_vector(const float2& x) { SkVector vector; x.store(&vector); return vector; } //////////////////////////////////////////////////////////////////////// int is_not_monotonic(SkScalar a, SkScalar b, SkScalar c) { SkScalar ab = a - b; SkScalar bc = b - c; if (ab < 0) { bc = -bc; } return ab == 0 || bc < 0; } //////////////////////////////////////////////////////////////////////// int valid_unit_divide(SkScalar numer, SkScalar denom, SkScalar* ratio) { SkASSERT(ratio); if (numer < 0) { numer = -numer; denom = -denom; } if (denom == 0 || numer == 0 || numer >= denom) { return 0; } SkScalar r = numer / denom; if (SkScalarIsNaN(r)) { return 0; } SkASSERTF(r >= 0 && r < SK_Scalar1, "numer %f, denom %f, r %f", numer, denom, r); if (r == 0) { // catch underflow if numer <<<< denom return 0; } *ratio = r; return 1; } // Just returns its argument, but makes it easy to set a break-point to know when // SkFindUnitQuadRoots is going to return 0 (an error). int return_check_zero(int value) { if (value == 0) { return 0; } return value; } } // namespace /** From Numerical Recipes in C. Q = -1/2 (B + sign(B) sqrt[B*B - 4*A*C]) x1 = Q / A x2 = C / Q */ int SkFindUnitQuadRoots(SkScalar A, SkScalar B, SkScalar C, SkScalar roots[2]) { SkASSERT(roots); if (A == 0) { return return_check_zero(valid_unit_divide(-C, B, roots)); } SkScalar* r = roots; // use doubles so we don't overflow temporarily trying to compute R double dr = (double)B * B - 4 * (double)A * C; if (dr < 0) { return return_check_zero(0); } dr = sqrt(dr); SkScalar R = SkDoubleToScalar(dr); if (!SkScalarIsFinite(R)) { return return_check_zero(0); } SkScalar Q = (B < 0) ? -(B-R)/2 : -(B+R)/2; r += valid_unit_divide(Q, A, r); r += valid_unit_divide(C, Q, r); if (r - roots == 2) { if (roots[0] > roots[1]) { using std::swap; swap(roots[0], roots[1]); } else if (roots[0] == roots[1]) { // nearly-equal? r -= 1; // skip the double root } } return return_check_zero((int)(r - roots)); } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// void SkEvalQuadAt(const SkPoint src[3], SkScalar t, SkPoint* pt, SkVector* tangent) { SkASSERT(src); SkASSERT(t >= 0 && t <= SK_Scalar1); if (pt) { *pt = SkEvalQuadAt(src, t); } if (tangent) { *tangent = SkEvalQuadTangentAt(src, t); } } SkPoint SkEvalQuadAt(const SkPoint src[3], SkScalar t) { return to_point(SkQuadCoeff(src).eval(t)); } SkVector SkEvalQuadTangentAt(const SkPoint src[3], SkScalar t) { // The derivative equation is 2(b - a +(a - 2b +c)t). This returns a // zero tangent vector when t is 0 or 1, and the control point is equal // to the end point. In this case, use the quad end points to compute the tangent. if ((t == 0 && src[0] == src[1]) || (t == 1 && src[1] == src[2])) { return src[2] - src[0]; } SkASSERT(src); SkASSERT(t >= 0 && t <= SK_Scalar1); float2 P0 = from_point(src[0]); float2 P1 = from_point(src[1]); float2 P2 = from_point(src[2]); float2 B = P1 - P0; float2 A = P2 - P1 - B; float2 T = A * t + B; return to_vector(T + T); } static inline float2 interp(const float2& v0, const float2& v1, const float2& t) { return v0 + (v1 - v0) * t; } void SkChopQuadAt(const SkPoint src[3], SkPoint dst[5], SkScalar t) { SkASSERT(t > 0 && t < SK_Scalar1); float2 p0 = from_point(src[0]); float2 p1 = from_point(src[1]); float2 p2 = from_point(src[2]); float2 tt(t); float2 p01 = interp(p0, p1, tt); float2 p12 = interp(p1, p2, tt); dst[0] = to_point(p0); dst[1] = to_point(p01); dst[2] = to_point(interp(p01, p12, tt)); dst[3] = to_point(p12); dst[4] = to_point(p2); } void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]) { SkChopQuadAt(src, dst, 0.5f); } float SkMeasureAngleBetweenVectors(SkVector a, SkVector b) { float cosTheta = sk_ieee_float_divide(a.dot(b), sqrtf(a.dot(a) * b.dot(b))); // Pin cosTheta such that if it is NaN (e.g., if a or b was 0), then we return acos(1) = 0. cosTheta = std::max(std::min(1.f, cosTheta), -1.f); return acosf(cosTheta); } SkVector SkFindBisector(SkVector a, SkVector b) { std::array v; if (a.dot(b) >= 0) { // a,b are within +/-90 degrees apart. v = {a, b}; } else if (a.cross(b) >= 0) { // a,b are >90 degrees apart. Find the bisector of their interior normals instead. (Above 90 // degrees, the original vectors start cancelling each other out which eventually becomes // unstable.) v[0].set(-a.fY, +a.fX); v[1].set(+b.fY, -b.fX); } else { // a,b are <-90 degrees apart. Find the bisector of their interior normals instead. (Below // -90 degrees, the original vectors start cancelling each other out which eventually // becomes unstable.) v[0].set(+a.fY, -a.fX); v[1].set(-b.fY, +b.fX); } // Return "normalize(v[0]) + normalize(v[1])". skvx::float2 x0_x1{v[0].fX, v[1].fX}; skvx::float2 y0_y1{v[0].fY, v[1].fY}; auto invLengths = 1.0f / sqrt(x0_x1 * x0_x1 + y0_y1 * y0_y1); x0_x1 *= invLengths; y0_y1 *= invLengths; return SkPoint{x0_x1[0] + x0_x1[1], y0_y1[0] + y0_y1[1]}; } float SkFindQuadMidTangent(const SkPoint src[3]) { // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the // midtangent. The bisector of tan0 and -tan1 is orthogonal to the midtangent: // // n dot midtangent = 0 // SkVector tan0 = src[1] - src[0]; SkVector tan1 = src[2] - src[1]; SkVector bisector = SkFindBisector(tan0, -tan1); // The midtangent can be found where (F' dot bisector) = 0: // // 0 = (F'(T) dot bisector) = |2*T 1| * |p0 - 2*p1 + p2| * |bisector.x| // |-2*p0 + 2*p1 | |bisector.y| // // = |2*T 1| * |tan1 - tan0| * |nx| // |2*tan0 | |ny| // // = 2*T * ((tan1 - tan0) dot bisector) + (2*tan0 dot bisector) // // T = (tan0 dot bisector) / ((tan0 - tan1) dot bisector) float T = sk_ieee_float_divide(tan0.dot(bisector), (tan0 - tan1).dot(bisector)); if (!(T > 0 && T < 1)) { // Use "!(positive_logic)" so T=nan will take this branch. T = .5; // The quadratic was a line or near-line. Just chop at .5. } return T; } /** Quad'(t) = At + B, where A = 2(a - 2b + c) B = 2(b - a) Solve for t, only if it fits skGeometry_between 0 < t < 1 */ int SkFindQuadExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar tValue[1]) { /* At + B == 0 t = -B / A */ return valid_unit_divide(a - b, a - b - b + c, tValue); } static inline void flatten_double_quad_extrema(SkScalar coords[14]) { coords[2] = coords[6] = coords[4]; } /* Returns 0 for 1 quad, and 1 for two quads, either way the answer is stored in dst[]. Guarantees that the 1/2 quads will be monotonic. */ int SkChopQuadAtYExtrema(const SkPoint src[3], SkPoint dst[5]) { SkASSERT(src); SkASSERT(dst); SkScalar a = src[0].fY; SkScalar b = src[1].fY; SkScalar c = src[2].fY; if (is_not_monotonic(a, b, c)) { SkScalar tValue; if (valid_unit_divide(a - b, a - b - b + c, &tValue)) { SkChopQuadAt(src, dst, tValue); flatten_double_quad_extrema(&dst[0].fY); return 1; } // if we get here, we need to force dst to be monotonic, even though // we couldn't compute a unit_divide value (probably underflow). b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c; } dst[0].set(src[0].fX, a); dst[1].set(src[1].fX, b); dst[2].set(src[2].fX, c); return 0; } /* Returns 0 for 1 quad, and 1 for two quads, either way the answer is stored in dst[]. Guarantees that the 1/2 quads will be monotonic. */ int SkChopQuadAtXExtrema(const SkPoint src[3], SkPoint dst[5]) { SkASSERT(src); SkASSERT(dst); SkScalar a = src[0].fX; SkScalar b = src[1].fX; SkScalar c = src[2].fX; if (is_not_monotonic(a, b, c)) { SkScalar tValue; if (valid_unit_divide(a - b, a - b - b + c, &tValue)) { SkChopQuadAt(src, dst, tValue); flatten_double_quad_extrema(&dst[0].fX); return 1; } // if we get here, we need to force dst to be monotonic, even though // we couldn't compute a unit_divide value (probably underflow). b = SkScalarAbs(a - b) < SkScalarAbs(b - c) ? a : c; } dst[0].set(a, src[0].fY); dst[1].set(b, src[1].fY); dst[2].set(c, src[2].fY); return 0; } // F(t) = a (1 - t) ^ 2 + 2 b t (1 - t) + c t ^ 2 // F'(t) = 2 (b - a) + 2 (a - 2b + c) t // F''(t) = 2 (a - 2b + c) // // A = 2 (b - a) // B = 2 (a - 2b + c) // // Maximum curvature for a quadratic means solving // Fx' Fx'' + Fy' Fy'' = 0 // // t = - (Ax Bx + Ay By) / (Bx ^ 2 + By ^ 2) // SkScalar SkFindQuadMaxCurvature(const SkPoint src[3]) { SkScalar Ax = src[1].fX - src[0].fX; SkScalar Ay = src[1].fY - src[0].fY; SkScalar Bx = src[0].fX - src[1].fX - src[1].fX + src[2].fX; SkScalar By = src[0].fY - src[1].fY - src[1].fY + src[2].fY; SkScalar numer = -(Ax * Bx + Ay * By); SkScalar denom = Bx * Bx + By * By; if (denom < 0) { numer = -numer; denom = -denom; } if (numer <= 0) { return 0; } if (numer >= denom) { // Also catches denom=0. return 1; } SkScalar t = numer / denom; SkASSERT((0 <= t && t < 1) || SkScalarIsNaN(t)); return t; } int SkChopQuadAtMaxCurvature(const SkPoint src[3], SkPoint dst[5]) { SkScalar t = SkFindQuadMaxCurvature(src); if (t > 0 && t < 1) { SkChopQuadAt(src, dst, t); return 2; } else { memcpy(dst, src, 3 * sizeof(SkPoint)); return 1; } } void SkConvertQuadToCubic(const SkPoint src[3], SkPoint dst[4]) { float2 scale(SkDoubleToScalar(2.0 / 3.0)); float2 s0 = from_point(src[0]); float2 s1 = from_point(src[1]); float2 s2 = from_point(src[2]); dst[0] = to_point(s0); dst[1] = to_point(s0 + (s1 - s0) * scale); dst[2] = to_point(s2 + (s1 - s2) * scale); dst[3] = to_point(s2); } ////////////////////////////////////////////////////////////////////////////// ///// CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS // CUBICS ///// ////////////////////////////////////////////////////////////////////////////// static SkVector eval_cubic_derivative(const SkPoint src[4], SkScalar t) { SkQuadCoeff coeff; float2 P0 = from_point(src[0]); float2 P1 = from_point(src[1]); float2 P2 = from_point(src[2]); float2 P3 = from_point(src[3]); coeff.fA = P3 + 3 * (P1 - P2) - P0; coeff.fB = times_2(P2 - times_2(P1) + P0); coeff.fC = P1 - P0; return to_vector(coeff.eval(t)); } static SkVector eval_cubic_2ndDerivative(const SkPoint src[4], SkScalar t) { float2 P0 = from_point(src[0]); float2 P1 = from_point(src[1]); float2 P2 = from_point(src[2]); float2 P3 = from_point(src[3]); float2 A = P3 + 3 * (P1 - P2) - P0; float2 B = P2 - times_2(P1) + P0; return to_vector(A * t + B); } void SkEvalCubicAt(const SkPoint src[4], SkScalar t, SkPoint* loc, SkVector* tangent, SkVector* curvature) { SkASSERT(src); SkASSERT(t >= 0 && t <= SK_Scalar1); if (loc) { *loc = to_point(SkCubicCoeff(src).eval(t)); } if (tangent) { // The derivative equation returns a zero tangent vector when t is 0 or 1, and the // adjacent control point is equal to the end point. In this case, use the // next control point or the end points to compute the tangent. if ((t == 0 && src[0] == src[1]) || (t == 1 && src[2] == src[3])) { if (t == 0) { *tangent = src[2] - src[0]; } else { *tangent = src[3] - src[1]; } if (!tangent->fX && !tangent->fY) { *tangent = src[3] - src[0]; } } else { *tangent = eval_cubic_derivative(src, t); } } if (curvature) { *curvature = eval_cubic_2ndDerivative(src, t); } } /** Cubic'(t) = At^2 + Bt + C, where A = 3(-a + 3(b - c) + d) B = 6(a - 2b + c) C = 3(b - a) Solve for t, keeping only those that fit betwee 0 < t < 1 */ int SkFindCubicExtrema(SkScalar a, SkScalar b, SkScalar c, SkScalar d, SkScalar tValues[2]) { // we divide A,B,C by 3 to simplify SkScalar A = d - a + 3*(b - c); SkScalar B = 2*(a - b - b + c); SkScalar C = b - a; return SkFindUnitQuadRoots(A, B, C, tValues); } // This does not return b when t==1, but it otherwise seems to get better precision than // "a*(1 - t) + b*t" for things like chopping cubics on exact cusp points. // The responsibility falls on the caller to check that t != 1 before calling. template inline static skvx::Vec unchecked_mix(const skvx::Vec& a, const skvx::Vec& b, const skvx::Vec& t) { return (b - a)*t + a; } void SkChopCubicAt(const SkPoint src[4], SkPoint dst[7], SkScalar t) { SkASSERT(0 <= t && t <= 1); if (t == 1) { memcpy(dst, src, sizeof(SkPoint) * 4); dst[4] = dst[5] = dst[6] = src[3]; return; } float2 p0 = sk_bit_cast(src[0]); float2 p1 = sk_bit_cast(src[1]); float2 p2 = sk_bit_cast(src[2]); float2 p3 = sk_bit_cast(src[3]); float2 T = t; float2 ab = unchecked_mix(p0, p1, T); float2 bc = unchecked_mix(p1, p2, T); float2 cd = unchecked_mix(p2, p3, T); float2 abc = unchecked_mix(ab, bc, T); float2 bcd = unchecked_mix(bc, cd, T); float2 abcd = unchecked_mix(abc, bcd, T); dst[0] = sk_bit_cast(p0); dst[1] = sk_bit_cast(ab); dst[2] = sk_bit_cast(abc); dst[3] = sk_bit_cast(abcd); dst[4] = sk_bit_cast(bcd); dst[5] = sk_bit_cast(cd); dst[6] = sk_bit_cast(p3); } void SkChopCubicAt(const SkPoint src[4], SkPoint dst[10], float t0, float t1) { SkASSERT(0 <= t0 && t0 <= t1 && t1 <= 1); if (t1 == 1) { SkChopCubicAt(src, dst, t0); dst[7] = dst[8] = dst[9] = src[3]; return; } // Perform both chops in parallel using 4-lane SIMD. float4 p00, p11, p22, p33, T; p00.lo = p00.hi = sk_bit_cast(src[0]); p11.lo = p11.hi = sk_bit_cast(src[1]); p22.lo = p22.hi = sk_bit_cast(src[2]); p33.lo = p33.hi = sk_bit_cast(src[3]); T.lo = t0; T.hi = t1; float4 ab = unchecked_mix(p00, p11, T); float4 bc = unchecked_mix(p11, p22, T); float4 cd = unchecked_mix(p22, p33, T); float4 abc = unchecked_mix(ab, bc, T); float4 bcd = unchecked_mix(bc, cd, T); float4 abcd = unchecked_mix(abc, bcd, T); float4 middle = unchecked_mix(abc, bcd, skvx::shuffle<2,3,0,1>(T)); dst[0] = sk_bit_cast(p00.lo); dst[1] = sk_bit_cast(ab.lo); dst[2] = sk_bit_cast(abc.lo); dst[3] = sk_bit_cast(abcd.lo); middle.store(dst + 4); dst[6] = sk_bit_cast(abcd.hi); dst[7] = sk_bit_cast(bcd.hi); dst[8] = sk_bit_cast(cd.hi); dst[9] = sk_bit_cast(p33.hi); } void SkChopCubicAt(const SkPoint src[4], SkPoint dst[], const SkScalar tValues[], int tCount) { SkASSERT(std::all_of(tValues, tValues + tCount, [](SkScalar t) { return t >= 0 && t <= 1; })); SkASSERT(std::is_sorted(tValues, tValues + tCount)); if (dst) { if (tCount == 0) { // nothing to chop memcpy(dst, src, 4*sizeof(SkPoint)); } else { int i = 0; for (; i < tCount - 1; i += 2) { // Do two chops at once. float2 tt = float2::Load(tValues + i); if (i != 0) { float lastT = tValues[i - 1]; tt = skvx::pin((tt - lastT) / (1 - lastT), float2(0), float2(1)); } SkChopCubicAt(src, dst, tt[0], tt[1]); src = dst = dst + 6; } if (i < tCount) { // Chop the final cubic if there was an odd number of chops. SkASSERT(i + 1 == tCount); float t = tValues[i]; if (i != 0) { float lastT = tValues[i - 1]; t = SkTPin(sk_ieee_float_divide(t - lastT, 1 - lastT), 0.f, 1.f); } SkChopCubicAt(src, dst, t); } } } } void SkChopCubicAtHalf(const SkPoint src[4], SkPoint dst[7]) { SkChopCubicAt(src, dst, 0.5f); } float SkMeasureNonInflectCubicRotation(const SkPoint pts[4]) { SkVector a = pts[1] - pts[0]; SkVector b = pts[2] - pts[1]; SkVector c = pts[3] - pts[2]; if (a.isZero()) { return SkMeasureAngleBetweenVectors(b, c); } if (b.isZero()) { return SkMeasureAngleBetweenVectors(a, c); } if (c.isZero()) { return SkMeasureAngleBetweenVectors(a, b); } // Postulate: When no points are colocated and there are no inflection points in T=0..1, the // rotation is: 360 degrees, minus the angle [p0,p1,p2], minus the angle [p1,p2,p3]. return 2*SK_ScalarPI - SkMeasureAngleBetweenVectors(a,-b) - SkMeasureAngleBetweenVectors(b,-c); } static skvx::float4 fma(const skvx::float4& f, float m, const skvx::float4& a) { return skvx::fma(f, skvx::float4(m), a); } // Finds the root nearest 0.5. Returns 0.5 if the roots are undefined or outside 0..1. static float solve_quadratic_equation_for_midtangent(float a, float b, float c, float discr) { // Quadratic formula from Numerical Recipes in C: float q = -.5f * (b + copysignf(sqrtf(discr), b)); // The roots are q/a and c/q. Pick the midtangent closer to T=.5. float _5qa = -.5f*q*a; float T = fabsf(q*q + _5qa) < fabsf(a*c + _5qa) ? sk_ieee_float_divide(q,a) : sk_ieee_float_divide(c,q); if (!(T > 0 && T < 1)) { // Use "!(positive_logic)" so T=NaN will take this branch. // Either the curve is a flat line with no rotation or FP precision failed us. Chop at .5. T = .5; } return T; } static float solve_quadratic_equation_for_midtangent(float a, float b, float c) { return solve_quadratic_equation_for_midtangent(a, b, c, b*b - 4*a*c); } float SkFindCubicMidTangent(const SkPoint src[4]) { // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the // midtangent. The bisector of tan0 and -tan1 is orthogonal to the midtangent: // // bisector dot midtangent == 0 // SkVector tan0 = (src[0] == src[1]) ? src[2] - src[0] : src[1] - src[0]; SkVector tan1 = (src[2] == src[3]) ? src[3] - src[1] : src[3] - src[2]; SkVector bisector = SkFindBisector(tan0, -tan1); // Find the T value at the midtangent. This is a simple quadratic equation: // // midtangent dot bisector == 0, or using a tangent matrix C' in power basis form: // // |C'x C'y| // |T^2 T 1| * |. . | * |bisector.x| == 0 // |. . | |bisector.y| // // The coeffs for the quadratic equation we need to solve are therefore: C' * bisector static const skvx::float4 kM[4] = {skvx::float4(-1, 2, -1, 0), skvx::float4( 3, -4, 1, 0), skvx::float4(-3, 2, 0, 0)}; auto C_x = fma(kM[0], src[0].fX, fma(kM[1], src[1].fX, fma(kM[2], src[2].fX, skvx::float4(src[3].fX, 0,0,0)))); auto C_y = fma(kM[0], src[0].fY, fma(kM[1], src[1].fY, fma(kM[2], src[2].fY, skvx::float4(src[3].fY, 0,0,0)))); auto coeffs = C_x * bisector.x() + C_y * bisector.y(); // Now solve the quadratic for T. float T = 0; float a=coeffs[0], b=coeffs[1], c=coeffs[2]; float discr = b*b - 4*a*c; if (discr > 0) { // This will only be false if the curve is a line. return solve_quadratic_equation_for_midtangent(a, b, c, discr); } else { // This is a 0- or 360-degree flat line. It doesn't have single points of midtangent. // (tangent == midtangent at every point on the curve except the cusp points.) // Chop in skGeometry_between both cusps instead, if any. There can be up to two cusps on a flat line, // both where the tangent is perpendicular to the starting tangent: // // tangent dot tan0 == 0 // coeffs = C_x * tan0.x() + C_y * tan0.y(); a = coeffs[0]; b = coeffs[1]; if (a != 0) { // We want the point in skGeometry_between both cusps. The midpoint of: // // (-b +/- sqrt(b^2 - 4*a*c)) / (2*a) // // Is equal to: // // -b / (2*a) T = -b / (2*a); } if (!(T > 0 && T < 1)) { // Use "!(positive_logic)" so T=NaN will take this branch. // Either the curve is a flat line with no rotation or FP precision failed us. Chop at // .5. T = .5; } return T; } } static void flatten_double_cubic_extrema(SkScalar coords[14]) { coords[4] = coords[8] = coords[6]; } /** Given 4 points on a cubic bezier, chop it into 1, 2, 3 beziers such that the resulting beziers are monotonic in Y. This is called by the scan converter. Depending on what is returned, dst[] is treated as follows: 0 dst[0..3] is the original cubic 1 dst[0..3] and dst[3..6] are the two new cubics 2 dst[0..3], dst[3..6], dst[6..9] are the three new cubics If dst == null, it is ignored and only the count is returned. */ int SkChopCubicAtYExtrema(const SkPoint src[4], SkPoint dst[10]) { SkScalar tValues[2]; int roots = SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, tValues); SkChopCubicAt(src, dst, tValues, roots); if (dst && roots > 0) { // we do some cleanup to ensure our Y extrema are flat flatten_double_cubic_extrema(&dst[0].fY); if (roots == 2) { flatten_double_cubic_extrema(&dst[3].fY); } } return roots; } int SkChopCubicAtXExtrema(const SkPoint src[4], SkPoint dst[10]) { SkScalar tValues[2]; int roots = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, tValues); SkChopCubicAt(src, dst, tValues, roots); if (dst && roots > 0) { // we do some cleanup to ensure our Y extrema are flat flatten_double_cubic_extrema(&dst[0].fX); if (roots == 2) { flatten_double_cubic_extrema(&dst[3].fX); } } return roots; } /** http://www.faculty.idc.ac.il/arik/quality/appendixA.html Inflection means that curvature is zero. Curvature is [F' x F''] / [F'^3] So we solve F'x X F''y - F'y X F''y == 0 After some canceling of the cubic term, we get A = b - a B = c - 2b + a C = d - 3c + 3b - a (BxCy - ByCx)t^2 + (AxCy - AyCx)t + AxBy - AyBx == 0 */ int SkFindCubicInflections(const SkPoint src[4], SkScalar tValues[2]) { SkScalar Ax = src[1].fX - src[0].fX; SkScalar Ay = src[1].fY - src[0].fY; SkScalar Bx = src[2].fX - 2 * src[1].fX + src[0].fX; SkScalar By = src[2].fY - 2 * src[1].fY + src[0].fY; SkScalar Cx = src[3].fX + 3 * (src[1].fX - src[2].fX) - src[0].fX; SkScalar Cy = src[3].fY + 3 * (src[1].fY - src[2].fY) - src[0].fY; return SkFindUnitQuadRoots(Bx*Cy - By*Cx, Ax*Cy - Ay*Cx, Ax*By - Ay*Bx, tValues); } int SkChopCubicAtInflections(const SkPoint src[4], SkPoint dst[10]) { SkScalar tValues[2]; int count = SkFindCubicInflections(src, tValues); if (dst) { if (count == 0) { memcpy(dst, src, 4 * sizeof(SkPoint)); } else { SkChopCubicAt(src, dst, tValues, count); } } return count + 1; } // Assumes the third component of points is 1. // Calcs p0 . (p1 x p2) static double calc_dot_cross_cubic(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) { const double xComp = (double) p0.fX * ((double) p1.fY - (double) p2.fY); const double yComp = (double) p0.fY * ((double) p2.fX - (double) p1.fX); const double wComp = (double) p1.fX * (double) p2.fY - (double) p1.fY * (double) p2.fX; return (xComp + yComp + wComp); } // Returns a positive power of 2 that, when multiplied by n, and excepting the two edge cases listed // below, shifts the exponent of n to yield a magnitude somewhere inside [1..2). // Returns 2^1023 if abs(n) < 2^-1022 (including 0). // Returns NaN if n is Inf or NaN. inline static double previous_inverse_pow2(double n) { uint64_t bits; memcpy(&bits, &n, sizeof(double)); bits = ((1023llu*2 << 52) + ((1llu << 52) - 1)) - bits; // exp=-exp bits &= (0x7ffllu) << 52; // mantissa=1.0, sign=0 memcpy(&n, &bits, sizeof(double)); return n; } inline static void write_cubic_inflection_roots(double t0, double s0, double t1, double s1, double* t, double* s) { t[0] = t0; s[0] = s0; // This copysign/abs business orients the implicit function so positive values are always on the // "left" side of the curve. t[1] = -copysign(t1, t1 * s1); s[1] = -fabs(s1); // Ensure t[0]/s[0] <= t[1]/s[1] (s[1] is negative from above). if (copysign(s[1], s[0]) * t[0] > -fabs(s[0]) * t[1]) { using std::swap; swap(t[0], t[1]); swap(s[0], s[1]); } } SkCubicType SkClassifyCubic(const SkPoint P[4], double t[2], double s[2], double d[4]) { // Find the cubic's inflection function, I = [T^3 -3T^2 3T -1] dot D. (D0 will always be 0 // for integral cubics.) // // See "Resolution Independent Curve Rendering using Programmable Graphics Hardware", // 4.2 Curve Categorization: // // https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf double A1 = calc_dot_cross_cubic(P[0], P[3], P[2]); double A2 = calc_dot_cross_cubic(P[1], P[0], P[3]); double A3 = calc_dot_cross_cubic(P[2], P[1], P[0]); double D3 = 3 * A3; double D2 = D3 - A2; double D1 = D2 - A2 + A1; // Shift the exponents in D so the largest magnitude falls somewhere in 1..2. This protects us // from overflow down the road while solving for roots and KLM functionals. double Dmax = std::max(std::max(fabs(D1), fabs(D2)), fabs(D3)); double norm = previous_inverse_pow2(Dmax); D1 *= norm; D2 *= norm; D3 *= norm; if (d) { d[3] = D3; d[2] = D2; d[1] = D1; d[0] = 0; } // Now use the inflection function to classify the cubic. // // See "Resolution Independent Curve Rendering using Programmable Graphics Hardware", // 4.4 Integral Cubics: // // https://www.microsoft.com/en-us/research/wp-content/uploads/2005/01/p1000-loop.pdf if (0 != D1) { double discr = 3*D2*D2 - 4*D1*D3; if (discr > 0) { // Serpentine. if (t && s) { double q = 3*D2 + copysign(sqrt(3*discr), D2); write_cubic_inflection_roots(q, 6*D1, 2*D3, q, t, s); } return SkCubicType::kSerpentine; } else if (discr < 0) { // Loop. if (t && s) { double q = D2 + copysign(sqrt(-discr), D2); write_cubic_inflection_roots(q, 2*D1, 2*(D2*D2 - D3*D1), D1*q, t, s); } return SkCubicType::kLoop; } else { // Cusp. if (t && s) { write_cubic_inflection_roots(D2, 2*D1, D2, 2*D1, t, s); } return SkCubicType::kLocalCusp; } } else { if (0 != D2) { // Cusp at T=infinity. if (t && s) { write_cubic_inflection_roots(D3, 3*D2, 1, 0, t, s); // T1=infinity. } return SkCubicType::kCuspAtInfinity; } else { // Degenerate. if (t && s) { write_cubic_inflection_roots(1, 0, 1, 0, t, s); // T0=T1=infinity. } return 0 != D3 ? SkCubicType::kQuadratic : SkCubicType::kLineOrPoint; } } } template void bubble_sort(T array[], int count) { for (int i = count - 1; i > 0; --i) for (int j = i; j > 0; --j) if (array[j] < array[j-1]) { T tmp(array[j]); array[j] = array[j-1]; array[j-1] = tmp; } } /** * Given an array and count, remove all pair-wise duplicates from the array, * keeping the existing sorting, and return the new count */ static int collaps_duplicates(SkScalar array[], int count) { for (int n = count; n > 1; --n) { if (array[0] == array[1]) { for (int i = 1; i < n; ++i) { array[i - 1] = array[i]; } count -= 1; } else { array += 1; } } return count; } #ifdef SK_DEBUG #define TEST_COLLAPS_ENTRY(array) array, std::size(array) static void test_collaps_duplicates() { static bool gOnce; if (gOnce) { return; } gOnce = true; const SkScalar src0[] = { 0 }; const SkScalar src1[] = { 0, 0 }; const SkScalar src2[] = { 0, 1 }; const SkScalar src3[] = { 0, 0, 0 }; const SkScalar src4[] = { 0, 0, 1 }; const SkScalar src5[] = { 0, 1, 1 }; const SkScalar src6[] = { 0, 1, 2 }; const struct { const SkScalar* fData; int fCount; int fCollapsedCount; } data[] = { { TEST_COLLAPS_ENTRY(src0), 1 }, { TEST_COLLAPS_ENTRY(src1), 1 }, { TEST_COLLAPS_ENTRY(src2), 2 }, { TEST_COLLAPS_ENTRY(src3), 1 }, { TEST_COLLAPS_ENTRY(src4), 2 }, { TEST_COLLAPS_ENTRY(src5), 2 }, { TEST_COLLAPS_ENTRY(src6), 3 }, }; for (size_t i = 0; i < std::size(data); ++i) { SkScalar dst[3]; memcpy(dst, data[i].fData, data[i].fCount * sizeof(dst[0])); int count = collaps_duplicates(dst, data[i].fCount); SkASSERT(data[i].fCollapsedCount == count); for (int j = 1; j < count; ++j) { SkASSERT(dst[j-1] < dst[j]); } } } #endif static SkScalar SkScalarCubeRoot(SkScalar x) { return SkScalarPow(x, 0.3333333f); } /* Solve coeff(t) == 0, returning the number of roots that lie withing 0 < t < 1. coeff[0]t^3 + coeff[1]t^2 + coeff[2]t + coeff[3] Eliminates repeated roots (so that all tValues are distinct, and are always in increasing order. */ static int solve_cubic_poly(const SkScalar coeff[4], SkScalar tValues[3]) { if (SkScalarNearlyZero(coeff[0])) { // we're just a quadratic return SkFindUnitQuadRoots(coeff[1], coeff[2], coeff[3], tValues); } SkScalar a, b, c, Q, R; { SkASSERT(coeff[0] != 0); SkScalar inva = SkScalarInvert(coeff[0]); a = coeff[1] * inva; b = coeff[2] * inva; c = coeff[3] * inva; } Q = (a*a - b*3) / 9; R = (2*a*a*a - 9*a*b + 27*c) / 54; SkScalar Q3 = Q * Q * Q; SkScalar R2MinusQ3 = R * R - Q3; SkScalar adiv3 = a / 3; if (R2MinusQ3 < 0) { // we have 3 real roots // the divide/root can, due to finite precisions, be slightly outside of -1...1 SkScalar theta = SkScalarACos(SkTPin(R / SkScalarSqrt(Q3), -1.0f, 1.0f)); SkScalar neg2RootQ = -2 * SkScalarSqrt(Q); tValues[0] = SkTPin(neg2RootQ * SkScalarCos(theta/3) - adiv3, 0.0f, 1.0f); tValues[1] = SkTPin(neg2RootQ * SkScalarCos((theta + 2*SK_ScalarPI)/3) - adiv3, 0.0f, 1.0f); tValues[2] = SkTPin(neg2RootQ * SkScalarCos((theta - 2*SK_ScalarPI)/3) - adiv3, 0.0f, 1.0f); SkDEBUGCODE(test_collaps_duplicates();) // now sort the roots bubble_sort(tValues, 3); return collaps_duplicates(tValues, 3); } else { // we have 1 real root SkScalar A = SkScalarAbs(R) + SkScalarSqrt(R2MinusQ3); A = SkScalarCubeRoot(A); if (R > 0) { A = -A; } if (A != 0) { A += Q / A; } tValues[0] = SkTPin(A - adiv3, 0.0f, 1.0f); return 1; } } /* Looking for F' dot F'' == 0 A = b - a B = c - 2b + a C = d - 3c + 3b - a F' = 3Ct^2 + 6Bt + 3A F'' = 6Ct + 6B F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB */ static void formulate_F1DotF2(const SkScalar src[], SkScalar coeff[4]) { SkScalar a = src[2] - src[0]; SkScalar b = src[4] - 2 * src[2] + src[0]; SkScalar c = src[6] + 3 * (src[2] - src[4]) - src[0]; coeff[0] = c * c; coeff[1] = 3 * b * c; coeff[2] = 2 * b * b + c * a; coeff[3] = a * b; } /* Looking for F' dot F'' == 0 A = b - a B = c - 2b + a C = d - 3c + 3b - a F' = 3Ct^2 + 6Bt + 3A F'' = 6Ct + 6B F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB */ int SkFindCubicMaxCurvature(const SkPoint src[4], SkScalar tValues[3]) { SkScalar coeffX[4], coeffY[4]; int i; formulate_F1DotF2(&src[0].fX, coeffX); formulate_F1DotF2(&src[0].fY, coeffY); for (i = 0; i < 4; i++) { coeffX[i] += coeffY[i]; } int numRoots = solve_cubic_poly(coeffX, tValues); // now remove extrema where the curvature is zero (mins) // !!!! need a test for this !!!! return numRoots; } int SkChopCubicAtMaxCurvature(const SkPoint src[4], SkPoint dst[13], SkScalar tValues[3]) { SkScalar t_storage[3]; if (tValues == nullptr) { tValues = t_storage; } SkScalar roots[3]; int rootCount = SkFindCubicMaxCurvature(src, roots); // Throw out values not inside 0..1. int count = 0; for (int i = 0; i < rootCount; ++i) { if (0 < roots[i] && roots[i] < 1) { tValues[count++] = roots[i]; } } if (dst) { if (count == 0) { memcpy(dst, src, 4 * sizeof(SkPoint)); } else { SkChopCubicAt(src, dst, tValues, count); } } return count + 1; } // Returns a constant proportional to the dimensions of the cubic. // Constant found through experimentation -- maybe there's a better way.... static SkScalar calc_cubic_precision(const SkPoint src[4]) { return (SkPointPriv::DistanceToSqd(src[1], src[0]) + SkPointPriv::DistanceToSqd(src[2], src[1]) + SkPointPriv::DistanceToSqd(src[3], src[2])) * 1e-8f; } // Returns true if both points src[testIndex], src[testIndex+1] are in the same half plane defined // by the line segment src[lineIndex], src[lineIndex+1]. static bool on_same_side(const SkPoint src[4], int testIndex, int lineIndex) { SkPoint origin = src[lineIndex]; SkVector line = src[lineIndex + 1] - origin; SkScalar crosses[2]; for (int index = 0; index < 2; ++index) { SkVector testLine = src[testIndex + index] - origin; crosses[index] = line.cross(testLine); } return crosses[0] * crosses[1] >= 0; } // Return location (in t) of cubic cusp, if there is one. // Note that classify cubic code does not reliably return all cusp'd cubics, so // it is not called here. SkScalar SkFindCubicCusp(const SkPoint src[4]) { // When the adjacent control point matches the end point, it behaves as if // the cubic has a cusp: there's a point of max curvature where the derivative // goes to zero. Ideally, this would be where t is zero or one, but math // error makes not so. It is not uncommon to create cubics this way; skip them. if (src[0] == src[1]) { return -1; } if (src[2] == src[3]) { return -1; } // Cubics only have a cusp if the line segments formed by the control and end points cross. // Detect crossing if line ends are on opposite sides of plane formed by the other line. if (on_same_side(src, 0, 2) || on_same_side(src, 2, 0)) { return -1; } // Cubics may have multiple points of maximum curvature, although at most only // one is a cusp. SkScalar maxCurvature[3]; int roots = SkFindCubicMaxCurvature(src, maxCurvature); for (int index = 0; index < roots; ++index) { SkScalar testT = maxCurvature[index]; if (0 >= testT || testT >= 1) { // no need to consider max curvature on the end continue; } // A cusp is at the max curvature, and also has a derivative close to zero. // Choose the 'close to zero' meaning by comparing the derivative length // with the overall cubic size. SkVector dPt = eval_cubic_derivative(src, testT); SkScalar dPtMagnitude = SkPointPriv::LengthSqd(dPt); SkScalar precision = calc_cubic_precision(src); if (dPtMagnitude < precision) { // All three max curvature t values may be close to the cusp; // return the first one. return testT; } } return -1; } static bool close_enough_to_zero(double x) { return std::fabs(x) < 0.00001; } static bool first_axis_intersection(const double coefficients[8], bool yDirection, double axisIntercept, double* solution) { auto [A, B, C, D] = SkBezierCubic::ConvertToPolynomial(coefficients, yDirection); D -= axisIntercept; double roots[3] = {0, 0, 0}; int count = SkCubics::RootsValidT(A, B, C, D, roots); if (count == 0) { return false; } // Verify that at least one of the roots is accurate. for (int i = 0; i < count; i++) { if (close_enough_to_zero(SkCubics::EvalAt(A, B, C, D, roots[i]))) { *solution = roots[i]; return true; } } // None of the roots returned by our normal cubic solver were correct enough // (e.g. https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=55732) // So we need to fallback to a more accurate solution. count = SkCubics::BinarySearchRootsValidT(A, B, C, D, roots); if (count == 0) { return false; } for (int i = 0; i < count; i++) { if (close_enough_to_zero(SkCubics::EvalAt(A, B, C, D, roots[i]))) { *solution = roots[i]; return true; } } return false; } bool SkChopMonoCubicAtY(const SkPoint src[4], SkScalar y, SkPoint dst[7]) { double coefficients[8] = {src[0].fX, src[0].fY, src[1].fX, src[1].fY, src[2].fX, src[2].fY, src[3].fX, src[3].fY}; double solution = 0; if (first_axis_intersection(coefficients, true, y, &solution)) { double cubicPair[14]; SkBezierCubic::Subdivide(coefficients, solution, cubicPair); for (int i = 0; i < 7; i ++) { dst[i].fX = sk_double_to_float(cubicPair[i*2]); dst[i].fY = sk_double_to_float(cubicPair[i*2 + 1]); } return true; } return false; } bool SkChopMonoCubicAtX(const SkPoint src[4], SkScalar x, SkPoint dst[7]) { double coefficients[8] = {src[0].fX, src[0].fY, src[1].fX, src[1].fY, src[2].fX, src[2].fY, src[3].fX, src[3].fY}; double solution = 0; if (first_axis_intersection(coefficients, false, x, &solution)) { double cubicPair[14]; SkBezierCubic::Subdivide(coefficients, solution, cubicPair); for (int i = 0; i < 7; i ++) { dst[i].fX = sk_double_to_float(cubicPair[i*2]); dst[i].fY = sk_double_to_float(cubicPair[i*2 + 1]); } return true; } return false; } /////////////////////////////////////////////////////////////////////////////// // // NURB representation for conics. Helpful explanations at: // // http://citeseerx.ist.psu.edu/viewdoc/ // download?doi=10.1.1.44.5740&rep=rep1&type=ps // and // http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS/RB-conics.html // // F = (A (1 - t)^2 + C t^2 + 2 B (1 - t) t w) // ------------------------------------------ // ((1 - t)^2 + t^2 + 2 (1 - t) t w) // // = {t^2 (P0 + P2 - 2 P1 w), t (-2 P0 + 2 P1 w), P0} // ------------------------------------------------ // {t^2 (2 - 2 w), t (-2 + 2 w), 1} // // F' = 2 (C t (1 + t (-1 + w)) - A (-1 + t) (t (-1 + w) - w) + B (1 - 2 t) w) // // t^2 : (2 P0 - 2 P2 - 2 P0 w + 2 P2 w) // t^1 : (-2 P0 + 2 P2 + 4 P0 w - 4 P1 w) // t^0 : -2 P0 w + 2 P1 w // // We disregard magnitude, so we can freely ignore the denominator of F', and // divide the numerator by 2 // // coeff[0] for t^2 // coeff[1] for t^1 // coeff[2] for t^0 // static void conic_deriv_coeff(const SkScalar src[], SkScalar w, SkScalar coeff[3]) { const SkScalar P20 = src[4] - src[0]; const SkScalar P10 = src[2] - src[0]; const SkScalar wP10 = w * P10; coeff[0] = w * P20 - P20; coeff[1] = P20 - 2 * wP10; coeff[2] = wP10; } static bool conic_find_extrema(const SkScalar src[], SkScalar w, SkScalar* t) { SkScalar coeff[3]; conic_deriv_coeff(src, w, coeff); SkScalar tValues[2]; int roots = SkFindUnitQuadRoots(coeff[0], coeff[1], coeff[2], tValues); SkASSERT(0 == roots || 1 == roots); if (1 == roots) { *t = tValues[0]; return true; } return false; } // We only interpolate one dimension at a time (the first, at +0, +3, +6). static void p3d_interp(const SkScalar src[7], SkScalar dst[7], SkScalar t) { SkScalar ab = SkScalarInterp(src[0], src[3], t); SkScalar bc = SkScalarInterp(src[3], src[6], t); dst[0] = ab; dst[3] = SkScalarInterp(ab, bc, t); dst[6] = bc; } static void ratquad_mapTo3D(const SkPoint src[3], SkScalar w, SkPoint3 dst[3]) { dst[0].set(src[0].fX * 1, src[0].fY * 1, 1); dst[1].set(src[1].fX * w, src[1].fY * w, w); dst[2].set(src[2].fX * 1, src[2].fY * 1, 1); } static SkPoint project_down(const SkPoint3& src) { return {src.fX / src.fZ, src.fY / src.fZ}; } // return false if infinity or NaN is generated; caller must check bool SkConic::chopAt(SkScalar t, SkConic dst[2]) const { SkPoint3 tmp[3], tmp2[3]; ratquad_mapTo3D(fPts, fW, tmp); p3d_interp(&tmp[0].fX, &tmp2[0].fX, t); p3d_interp(&tmp[0].fY, &tmp2[0].fY, t); p3d_interp(&tmp[0].fZ, &tmp2[0].fZ, t); dst[0].fPts[0] = fPts[0]; dst[0].fPts[1] = project_down(tmp2[0]); dst[0].fPts[2] = project_down(tmp2[1]); dst[1].fPts[0] = dst[0].fPts[2]; dst[1].fPts[1] = project_down(tmp2[2]); dst[1].fPts[2] = fPts[2]; // to put in "standard form", where w0 and w2 are both 1, we compute the // new w1 as sqrt(w1*w1/w0*w2) // or // w1 /= sqrt(w0*w2) // // However, in our case, we know that for dst[0]: // w0 == 1, and for dst[1], w2 == 1 // SkScalar root = SkScalarSqrt(tmp2[1].fZ); dst[0].fW = tmp2[0].fZ / root; dst[1].fW = tmp2[2].fZ / root; SkASSERT(sizeof(dst[0]) == sizeof(SkScalar) * 7); SkASSERT(0 == offsetof(SkConic, fPts[0].fX)); return SkScalarsAreFinite(&dst[0].fPts[0].fX, 7 * 2); } void SkConic::chopAt(SkScalar t1, SkScalar t2, SkConic* dst) const { if (0 == t1 || 1 == t2) { if (0 == t1 && 1 == t2) { *dst = *this; return; } else { SkConic pair[2]; if (this->chopAt(t1 ? t1 : t2, pair)) { *dst = pair[SkToBool(t1)]; return; } } } SkConicCoeff coeff(*this); float2 tt1(t1); float2 aXY = coeff.fNumer.eval(tt1); float2 aZZ = coeff.fDenom.eval(tt1); float2 midTT((t1 + t2) / 2); float2 dXY = coeff.fNumer.eval(midTT); float2 dZZ = coeff.fDenom.eval(midTT); float2 tt2(t2); float2 cXY = coeff.fNumer.eval(tt2); float2 cZZ = coeff.fDenom.eval(tt2); float2 bXY = times_2(dXY) - (aXY + cXY) * 0.5f; float2 bZZ = times_2(dZZ) - (aZZ + cZZ) * 0.5f; dst->fPts[0] = to_point(aXY / aZZ); dst->fPts[1] = to_point(bXY / bZZ); dst->fPts[2] = to_point(cXY / cZZ); float2 ww = bZZ / sqrt(aZZ * cZZ); dst->fW = ww[0]; } SkPoint SkConic::evalAt(SkScalar t) const { return to_point(SkConicCoeff(*this).eval(t)); } SkVector SkConic::evalTangentAt(SkScalar t) const { // The derivative equation returns a zero tangent vector when t is 0 or 1, // and the control point is equal to the end point. // In this case, use the conic endpoints to compute the tangent. if ((t == 0 && fPts[0] == fPts[1]) || (t == 1 && fPts[1] == fPts[2])) { return fPts[2] - fPts[0]; } float2 p0 = from_point(fPts[0]); float2 p1 = from_point(fPts[1]); float2 p2 = from_point(fPts[2]); float2 ww(fW); float2 p20 = p2 - p0; float2 p10 = p1 - p0; float2 C = ww * p10; float2 A = ww * p20 - p20; float2 B = p20 - C - C; return to_vector(SkQuadCoeff(A, B, C).eval(t)); } void SkConic::evalAt(SkScalar t, SkPoint* pt, SkVector* tangent) const { SkASSERT(t >= 0 && t <= SK_Scalar1); if (pt) { *pt = this->evalAt(t); } if (tangent) { *tangent = this->evalTangentAt(t); } } static SkScalar subdivide_w_value(SkScalar w) { return SkScalarSqrt(SK_ScalarHalf + w * SK_ScalarHalf); } #if defined(SK_SUPPORT_LEGACY_CONIC_CHOP) void SkConic::chop(SkConic * SK_RESTRICT dst) const { float2 scale = SkScalarInvert(SK_Scalar1 + fW); SkScalar newW = subdivide_w_value(fW); float2 p0 = from_point(fPts[0]); float2 p1 = from_point(fPts[1]); float2 p2 = from_point(fPts[2]); float2 ww(fW); float2 wp1 = ww * p1; float2 m = (p0 + times_2(wp1) + p2) * scale * 0.5f; SkPoint mPt = to_point(m); if (!mPt.isFinite()) { double w_d = fW; double w_2 = w_d * 2; double scale_half = 1 / (1 + w_d) * 0.5; mPt.fX = SkDoubleToScalar((fPts[0].fX + w_2 * fPts[1].fX + fPts[2].fX) * scale_half); mPt.fY = SkDoubleToScalar((fPts[0].fY + w_2 * fPts[1].fY + fPts[2].fY) * scale_half); } dst[0].fPts[0] = fPts[0]; dst[0].fPts[1] = to_point((p0 + wp1) * scale); dst[0].fPts[2] = dst[1].fPts[0] = mPt; dst[1].fPts[1] = to_point((wp1 + p2) * scale); dst[1].fPts[2] = fPts[2]; dst[0].fW = dst[1].fW = newW; } #else void SkConic::chop(SkConic * SK_RESTRICT dst) const { // Observe that scale will always be smaller than 1 because fW > 0. const float scale = SkScalarInvert(SK_Scalar1 + fW); // The subdivided control points below are the sums of the following three terms. Because the // terms are multiplied by something <1, and the resulting control points lie within the // control points of the original then the terms and the sums below will not overflow. Note // that fW * scale approaches 1 as fW becomes very large. float2 t0 = from_point(fPts[0]) * scale; float2 t1 = from_point(fPts[1]) * (fW * scale); float2 t2 = from_point(fPts[2]) * scale; // Calculate the subdivided control points const SkPoint p1 = to_point(t0 + t1); const SkPoint p3 = to_point(t1 + t2); // p2 = (t0 + 2*t1 + t2) / 2. Divide the terms by 2 before the sum to keep the sum for p2 // from overflowing. const SkPoint p2 = to_point(0.5f * t0 + t1 + 0.5f * t2); SkASSERT(p1.isFinite() && p2.isFinite() && p3.isFinite()); dst[0].fPts[0] = fPts[0]; dst[0].fPts[1] = p1; dst[0].fPts[2] = p2; dst[1].fPts[0] = p2; dst[1].fPts[1] = p3; dst[1].fPts[2] = fPts[2]; // Update w. dst[0].fW = dst[1].fW = subdivide_w_value(fW); } #endif // SK_SUPPORT_LEGACY_CONIC_CHOP /* * "High order approximation of conic sections by quadratic splines" * by Michael Floater, 1993 */ #define AS_QUAD_ERROR_SETUP \ SkScalar a = fW - 1; \ SkScalar k = a / (4 * (2 + a)); \ SkScalar x = k * (fPts[0].fX - 2 * fPts[1].fX + fPts[2].fX); \ SkScalar y = k * (fPts[0].fY - 2 * fPts[1].fY + fPts[2].fY); void SkConic::computeAsQuadError(SkVector* err) const { AS_QUAD_ERROR_SETUP err->set(x, y); } bool SkConic::asQuadTol(SkScalar tol) const { AS_QUAD_ERROR_SETUP return (x * x + y * y) <= tol * tol; } // Limit the number of suggested quads to approximate a conic #define kMaxConicToQuadPOW2 5 int SkConic::computeQuadPOW2(SkScalar tol) const { if (tol < 0 || !SkScalarIsFinite(tol) || !SkPointPriv::AreFinite(fPts, 3)) { return 0; } AS_QUAD_ERROR_SETUP SkScalar error = SkScalarSqrt(x * x + y * y); int pow2; for (pow2 = 0; pow2 < kMaxConicToQuadPOW2; ++pow2) { if (error <= tol) { break; } error *= 0.25f; } // float version -- using ceil gives the same results as the above. if ((false)) { SkScalar err = SkScalarSqrt(x * x + y * y); if (err <= tol) { return 0; } SkScalar tol2 = tol * tol; if (tol2 == 0) { return kMaxConicToQuadPOW2; } SkScalar fpow2 = SkScalarLog2((x * x + y * y) / tol2) * 0.25f; int altPow2 = SkScalarCeilToInt(fpow2); if (altPow2 != pow2) { SkDebugf("pow2 %d altPow2 %d fbits %g err %g tol %g\n", pow2, altPow2, fpow2, err, tol); } pow2 = altPow2; } return pow2; } // This was originally developed and tested for pathops: see SkOpTypes.h // returns true if (a <= b <= c) || (a >= b >= c) static bool skGeometry_between(SkScalar a, SkScalar b, SkScalar c) { return (a - b) * (c - b) <= 0; } static SkPoint* subdivide(const SkConic& src, SkPoint pts[], int level) { SkASSERT(level >= 0); if (0 == level) { memcpy(pts, &src.fPts[1], 2 * sizeof(SkPoint)); return pts + 2; } else { SkConic dst[2]; src.chop(dst); const SkScalar startY = src.fPts[0].fY; SkScalar endY = src.fPts[2].fY; if (skGeometry_between(startY, src.fPts[1].fY, endY)) { // If the input is monotonic and the output is not, the scan converter hangs. // Ensure that the chopped conics maintain their y-order. SkScalar midY = dst[0].fPts[2].fY; if (!skGeometry_between(startY, midY, endY)) { // If the computed midpoint is outside the ends, move it to the closer one. SkScalar closerY = SkTAbs(midY - startY) < SkTAbs(midY - endY) ? startY : endY; dst[0].fPts[2].fY = dst[1].fPts[0].fY = closerY; } if (!skGeometry_between(startY, dst[0].fPts[1].fY, dst[0].fPts[2].fY)) { // If the 1st control is not skGeometry_between the start and end, put it at the start. // This also reduces the quad to a line. dst[0].fPts[1].fY = startY; } if (!skGeometry_between(dst[1].fPts[0].fY, dst[1].fPts[1].fY, endY)) { // If the 2nd control is not skGeometry_between the start and end, put it at the end. // This also reduces the quad to a line. dst[1].fPts[1].fY = endY; } // Verify that all five points are in order. SkASSERT(skGeometry_between(startY, dst[0].fPts[1].fY, dst[0].fPts[2].fY)); SkASSERT(skGeometry_between(dst[0].fPts[1].fY, dst[0].fPts[2].fY, dst[1].fPts[1].fY)); SkASSERT(skGeometry_between(dst[0].fPts[2].fY, dst[1].fPts[1].fY, endY)); } --level; pts = subdivide(dst[0], pts, level); return subdivide(dst[1], pts, level); } } int SkConic::chopIntoQuadsPOW2(SkPoint pts[], int pow2) const { SkASSERT(pow2 >= 0); *pts = fPts[0]; SkDEBUGCODE(SkPoint* endPts); if (pow2 == kMaxConicToQuadPOW2) { // If an extreme weight generates many quads ... SkConic dst[2]; this->chop(dst); // check to see if the first chop generates a pair of lines if (SkPointPriv::EqualsWithinTolerance(dst[0].fPts[1], dst[0].fPts[2]) && SkPointPriv::EqualsWithinTolerance(dst[1].fPts[0], dst[1].fPts[1])) { pts[1] = pts[2] = pts[3] = dst[0].fPts[1]; // set ctrl == end to make lines pts[4] = dst[1].fPts[2]; pow2 = 1; SkDEBUGCODE(endPts = &pts[5]); goto commonFinitePtCheck; } } SkDEBUGCODE(endPts = ) subdivide(*this, pts + 1, pow2); commonFinitePtCheck: const int quadCount = 1 << pow2; const int ptCount = 2 * quadCount + 1; SkASSERT(endPts - pts == ptCount); if (!SkPointPriv::AreFinite(pts, ptCount)) { // if we generated a non-finite, pin ourselves to the middle of the hull, // as our first and last are already on the first/last pts of the hull. for (int i = 1; i < ptCount - 1; ++i) { pts[i] = fPts[1]; } } return 1 << pow2; } float SkConic::findMidTangent() const { // Tangents point in the direction of increasing T, so tan0 and -tan1 both point toward the // midtangent. The bisector of tan0 and -tan1 is orthogonal to the midtangent: // // bisector dot midtangent = 0 // SkVector tan0 = fPts[1] - fPts[0]; SkVector tan1 = fPts[2] - fPts[1]; SkVector bisector = SkFindBisector(tan0, -tan1); // Start by finding the tangent function's power basis coefficients. These define a tangent // direction (scaled by some uniform value) as: // |T^2| // Tangent_Direction(T) = dx,dy = |A B C| * |T | // |. . .| |1 | // // The derivative of a conic has a cumbersome order-4 denominator. However, this isn't necessary // if we are only interested in a vector in the same *direction* as a given tangent line. Since // the denominator scales dx and dy uniformly, we can throw it out completely after evaluating // the derivative with the standard quotient rule. This leaves us with a simpler quadratic // function that we use to find a tangent. SkVector A = (fPts[2] - fPts[0]) * (fW - 1); SkVector B = (fPts[2] - fPts[0]) - (fPts[1] - fPts[0]) * (fW*2); SkVector C = (fPts[1] - fPts[0]) * fW; // Now solve for "bisector dot midtangent = 0": // // |T^2| // bisector * |A B C| * |T | = 0 // |. . .| |1 | // float a = bisector.dot(A); float b = bisector.dot(B); float c = bisector.dot(C); return solve_quadratic_equation_for_midtangent(a, b, c); } bool SkConic::findXExtrema(SkScalar* t) const { return conic_find_extrema(&fPts[0].fX, fW, t); } bool SkConic::findYExtrema(SkScalar* t) const { return conic_find_extrema(&fPts[0].fY, fW, t); } bool SkConic::chopAtXExtrema(SkConic dst[2]) const { SkScalar t; if (this->findXExtrema(&t)) { if (!this->chopAt(t, dst)) { // if chop can't return finite values, don't chop return false; } // now clean-up the middle, since we know t was meant to be at // an X-extrema SkScalar value = dst[0].fPts[2].fX; dst[0].fPts[1].fX = value; dst[1].fPts[0].fX = value; dst[1].fPts[1].fX = value; return true; } return false; } bool SkConic::chopAtYExtrema(SkConic dst[2]) const { SkScalar t; if (this->findYExtrema(&t)) { if (!this->chopAt(t, dst)) { // if chop can't return finite values, don't chop return false; } // now clean-up the middle, since we know t was meant to be at // an Y-extrema SkScalar value = dst[0].fPts[2].fY; dst[0].fPts[1].fY = value; dst[1].fPts[0].fY = value; dst[1].fPts[1].fY = value; return true; } return false; } void SkConic::computeTightBounds(SkRect* bounds) const { SkPoint pts[4]; pts[0] = fPts[0]; pts[1] = fPts[2]; int count = 2; SkScalar t; if (this->findXExtrema(&t)) { this->evalAt(t, &pts[count++]); } if (this->findYExtrema(&t)) { this->evalAt(t, &pts[count++]); } bounds->setBounds(pts, count); } void SkConic::computeFastBounds(SkRect* bounds) const { bounds->setBounds(fPts, 3); } #if 0 // unimplemented bool SkConic::findMaxCurvature(SkScalar* t) const { // TODO: Implement me return false; } #endif SkScalar SkConic::TransformW(const SkPoint pts[3], SkScalar w, const SkMatrix& matrix) { if (!matrix.hasPerspective()) { return w; } SkPoint3 src[3], dst[3]; ratquad_mapTo3D(pts, w, src); matrix.mapHomogeneousPoints(dst, src, 3); // w' = sqrt(w1*w1/w0*w2) // use doubles temporarily, to handle small numer/denom double w0 = dst[0].fZ; double w1 = dst[1].fZ; double w2 = dst[2].fZ; return sk_double_to_float(sqrt(sk_ieee_double_divide(w1 * w1, w0 * w2))); } int SkConic::BuildUnitArc(const SkVector& uStart, const SkVector& uStop, SkRotationDirection dir, const SkMatrix* userMatrix, SkConic dst[kMaxConicsForArc]) { // rotate by x,y so that uStart is (1.0) SkScalar x = SkPoint::DotProduct(uStart, uStop); SkScalar y = SkPoint::CrossProduct(uStart, uStop); SkScalar absY = SkScalarAbs(y); // check for (effectively) coincident vectors // this can happen if our angle is nearly 0 or nearly 180 (y == 0) // ... we use the dot-prod to distinguish skGeometry_between 0 and 180 (x > 0) if (absY <= SK_ScalarNearlyZero && x > 0 && ((y >= 0 && kCW_SkRotationDirection == dir) || (y <= 0 && kCCW_SkRotationDirection == dir))) { return 0; } if (dir == kCCW_SkRotationDirection) { y = -y; } // We decide to use 1-conic per quadrant of a circle. What quadrant does [xy] lie in? // 0 == [0 .. 90) // 1 == [90 ..180) // 2 == [180..270) // 3 == [270..360) // int quadrant = 0; if (0 == y) { quadrant = 2; // 180 SkASSERT(SkScalarAbs(x + SK_Scalar1) <= SK_ScalarNearlyZero); } else if (0 == x) { SkASSERT(absY - SK_Scalar1 <= SK_ScalarNearlyZero); quadrant = y > 0 ? 1 : 3; // 90 : 270 } else { if (y < 0) { quadrant += 2; } if ((x < 0) != (y < 0)) { quadrant += 1; } } const SkPoint quadrantPts[] = { { 1, 0 }, { 1, 1 }, { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 }, { 0, -1 }, { 1, -1 } }; const SkScalar quadrantWeight = SK_ScalarRoot2Over2; int conicCount = quadrant; for (int i = 0; i < conicCount; ++i) { dst[i].set(&quadrantPts[i * 2], quadrantWeight); } // Now compute any remaing (sub-90-degree) arc for the last conic const SkPoint finalP = { x, y }; const SkPoint& lastQ = quadrantPts[quadrant * 2]; // will already be a unit-vector const SkScalar dot = SkVector::DotProduct(lastQ, finalP); SkASSERT(0 <= dot && dot <= SK_Scalar1 + SK_ScalarNearlyZero); if (dot < 1) { SkVector offCurve = { lastQ.x() + x, lastQ.y() + y }; // compute the bisector vector, and then rescale to be the off-curve point. // we compute its length from cos(theta/2) = length / 1, using half-angle identity we get // length = sqrt(2 / (1 + cos(theta)). We already have cos() when to computed the dot. // This is nice, since our computed weight is cos(theta/2) as well! // const SkScalar cosThetaOver2 = SkScalarSqrt((1 + dot) / 2); offCurve.setLength(SkScalarInvert(cosThetaOver2)); if (!SkPointPriv::EqualsWithinTolerance(lastQ, offCurve)) { dst[conicCount].set(lastQ, offCurve, finalP, cosThetaOver2); conicCount += 1; } } // now handle counter-clockwise and the initial unitStart rotation SkMatrix matrix; matrix.setSinCos(uStart.fY, uStart.fX); if (dir == kCCW_SkRotationDirection) { matrix.preScale(SK_Scalar1, -SK_Scalar1); } if (userMatrix) { matrix.postConcat(*userMatrix); } for (int i = 0; i < conicCount; ++i) { matrix.mapPoints(dst[i].fPts, 3); } return conicCount; } /** * Used to be notified when a gen/unique ID is invalidated, typically to preemptively purge * associated items from a cache that are no longer reachable. The listener can * be marked for deregistration if the cached item is remove before the listener is * triggered. This prevents unbounded listener growth when cache items are routinely * removed before the gen ID/unique ID is invalidated. */ SkIDChangeListener::SkIDChangeListener() : fShouldDeregister(false) {} SkIDChangeListener::~SkIDChangeListener() = default; using List = SkIDChangeListener::List; List::List() = default; List::~List() { // We don't need the mutex. No other thread should have this list while it's being // destroyed. for (auto& listener : fListeners) { if (!listener->shouldDeregister()) { listener->changed(); } } } void List::add(sk_sp listener) { if (!listener) { return; } SkASSERT(!listener->shouldDeregister()); SkAutoMutexExclusive lock(fMutex); // Clean out any stale listeners before we append the new one. for (int i = 0; i < fListeners.size(); ++i) { if (fListeners[i]->shouldDeregister()) { fListeners.removeShuffle(i--); // No need to preserve the order after i. } } fListeners.push_back(std::move(listener)); } int List::count() const { SkAutoMutexExclusive lock(fMutex); return fListeners.size(); } void List::changed() { SkAutoMutexExclusive lock(fMutex); for (auto& listener : fListeners) { if (!listener->shouldDeregister()) { listener->changed(); } } fListeners.clear(); } void List::reset() { SkAutoMutexExclusive lock(fMutex); fListeners.clear(); } template T pin_unsorted(T value, T limit0, T limit1) { if (limit1 < limit0) { using std::swap; swap(limit0, limit1); } // now the limits are sorted SkASSERT(limit0 <= limit1); if (value < limit0) { value = limit0; } else if (value > limit1) { value = limit1; } return value; } // return X coordinate of intersection with horizontal line at Y static SkScalar sect_with_horizontal(const SkPoint src[2], SkScalar Y) { SkScalar dy = src[1].fY - src[0].fY; if (SkScalarNearlyZero(dy)) { return SkScalarAve(src[0].fX, src[1].fX); } else { // need the extra precision so we don't compute a value that exceeds // our original limits double X0 = src[0].fX; double Y0 = src[0].fY; double X1 = src[1].fX; double Y1 = src[1].fY; double result = X0 + ((double)Y - Y0) * (X1 - X0) / (Y1 - Y0); // The computed X value might still exceed [X0..X1] due to quantum flux // when the doubles were added and subtracted, so we have to pin the // answer :( return (float)pin_unsorted(result, X0, X1); } } // return Y coordinate of intersection with vertical line at X static SkScalar sect_with_vertical(const SkPoint src[2], SkScalar X) { SkScalar dx = src[1].fX - src[0].fX; if (SkScalarNearlyZero(dx)) { return SkScalarAve(src[0].fY, src[1].fY); } else { // need the extra precision so we don't compute a value that exceeds // our original limits double X0 = src[0].fX; double Y0 = src[0].fY; double X1 = src[1].fX; double Y1 = src[1].fY; double result = Y0 + ((double)X - X0) * (Y1 - Y0) / (X1 - X0); return (float)result; } } static SkScalar sect_clamp_with_vertical(const SkPoint src[2], SkScalar x) { SkScalar y = sect_with_vertical(src, x); // Our caller expects y to be between src[0].fY and src[1].fY (unsorted), but due to the // numerics of floats/doubles, we might have computed a value slightly outside of that, // so we have to manually clamp afterwards. // See skbug.com/7491 return pin_unsorted(y, src[0].fY, src[1].fY); } /////////////////////////////////////////////////////////////////////////////// static inline bool nestedLT(SkScalar a, SkScalar b, SkScalar dim) { return a <= b && (a < b || dim > 0); } // returns true if outer contains inner, even if inner is empty. // note: outer.contains(inner) always returns false if inner is empty. static inline bool containsNoEmptyCheck(const SkRect& outer, const SkRect& inner) { return outer.fLeft <= inner.fLeft && outer.fTop <= inner.fTop && outer.fRight >= inner.fRight && outer.fBottom >= inner.fBottom; } bool SkLineClipper::IntersectLine(const SkPoint src[2], const SkRect& clip, SkPoint dst[2]) { SkRect bounds; bounds.set(src[0], src[1]); if (containsNoEmptyCheck(clip, bounds)) { if (src != dst) { memcpy(dst, src, 2 * sizeof(SkPoint)); } return true; } // check for no overlap, and only permit coincident edges if the line // and the edge are colinear if (nestedLT(bounds.fRight, clip.fLeft, bounds.width()) || nestedLT(clip.fRight, bounds.fLeft, bounds.width()) || nestedLT(bounds.fBottom, clip.fTop, bounds.height()) || nestedLT(clip.fBottom, bounds.fTop, bounds.height())) { return false; } int index0, index1; if (src[0].fY < src[1].fY) { index0 = 0; index1 = 1; } else { index0 = 1; index1 = 0; } SkPoint tmp[2]; memcpy(tmp, src, sizeof(tmp)); // now compute Y intersections if (tmp[index0].fY < clip.fTop) { tmp[index0].set(sect_with_horizontal(src, clip.fTop), clip.fTop); } if (tmp[index1].fY > clip.fBottom) { tmp[index1].set(sect_with_horizontal(src, clip.fBottom), clip.fBottom); } if (tmp[0].fX < tmp[1].fX) { index0 = 0; index1 = 1; } else { index0 = 1; index1 = 0; } // check for quick-reject in X again, now that we may have been chopped if ((tmp[index1].fX <= clip.fLeft || tmp[index0].fX >= clip.fRight)) { // usually we will return false, but we don't if the line is vertical and coincident // with the clip. if (tmp[0].fX != tmp[1].fX || tmp[0].fX < clip.fLeft || tmp[0].fX > clip.fRight) { return false; } } if (tmp[index0].fX < clip.fLeft) { tmp[index0].set(clip.fLeft, sect_with_vertical(tmp, clip.fLeft)); } if (tmp[index1].fX > clip.fRight) { tmp[index1].set(clip.fRight, sect_with_vertical(tmp, clip.fRight)); } #ifdef SK_DEBUG bounds.set(tmp[0], tmp[1]); SkASSERT(containsNoEmptyCheck(clip, bounds)); #endif memcpy(dst, tmp, sizeof(tmp)); return true; } #ifdef SK_DEBUG // return value between the two limits, where the limits are either ascending // or descending. static bool is_between_unsorted(SkScalar value, SkScalar limit0, SkScalar limit1) { if (limit0 < limit1) { return limit0 <= value && value <= limit1; } else { return limit1 <= value && value <= limit0; } } #endif int SkLineClipper::ClipLine(const SkPoint pts[2], const SkRect& clip, SkPoint lines[kMaxPoints], bool canCullToTheRight) { int index0, index1; if (pts[0].fY < pts[1].fY) { index0 = 0; index1 = 1; } else { index0 = 1; index1 = 0; } // Check if we're completely clipped out in Y (above or below if (pts[index1].fY <= clip.fTop) { // we're above the clip return 0; } if (pts[index0].fY >= clip.fBottom) { // we're below the clip return 0; } // Chop in Y to produce a single segment, stored in tmp[0..1] SkPoint tmp[2]; memcpy(tmp, pts, sizeof(tmp)); // now compute intersections if (pts[index0].fY < clip.fTop) { tmp[index0].set(sect_with_horizontal(pts, clip.fTop), clip.fTop); SkASSERT(is_between_unsorted(tmp[index0].fX, pts[0].fX, pts[1].fX)); } if (tmp[index1].fY > clip.fBottom) { tmp[index1].set(sect_with_horizontal(pts, clip.fBottom), clip.fBottom); SkASSERT(is_between_unsorted(tmp[index1].fX, pts[0].fX, pts[1].fX)); } // Chop it into 1..3 segments that are wholly within the clip in X. // temp storage for up to 3 segments SkPoint resultStorage[kMaxPoints]; SkPoint* result; // points to our results, either tmp or resultStorage int lineCount = 1; bool reverse; if (pts[0].fX < pts[1].fX) { index0 = 0; index1 = 1; reverse = false; } else { index0 = 1; index1 = 0; reverse = true; } if (tmp[index1].fX <= clip.fLeft) { // wholly to the left tmp[0].fX = tmp[1].fX = clip.fLeft; result = tmp; reverse = false; } else if (tmp[index0].fX >= clip.fRight) { // wholly to the right if (canCullToTheRight) { return 0; } tmp[0].fX = tmp[1].fX = clip.fRight; result = tmp; reverse = false; } else { result = resultStorage; SkPoint* r = result; if (tmp[index0].fX < clip.fLeft) { r->set(clip.fLeft, tmp[index0].fY); r += 1; r->set(clip.fLeft, sect_clamp_with_vertical(tmp, clip.fLeft)); SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY)); } else { *r = tmp[index0]; } r += 1; if (tmp[index1].fX > clip.fRight) { r->set(clip.fRight, sect_clamp_with_vertical(tmp, clip.fRight)); SkASSERT(is_between_unsorted(r->fY, tmp[0].fY, tmp[1].fY)); r += 1; r->set(clip.fRight, tmp[index1].fY); } else { *r = tmp[index1]; } lineCount = SkToInt(r - result); } // Now copy the results into the caller's lines[] parameter if (reverse) { // copy the pts in reverse order to maintain winding order for (int i = 0; i <= lineCount; i++) { lines[lineCount - i] = result[i]; } } else { memcpy(lines, result, (lineCount + 1) * sizeof(SkPoint)); } return lineCount; } bool SkM44::operator==(const SkM44& other) const { if (this == &other) { return true; } auto a0 = skvx::float4::Load(fMat + 0); auto a1 = skvx::float4::Load(fMat + 4); auto a2 = skvx::float4::Load(fMat + 8); auto a3 = skvx::float4::Load(fMat + 12); auto b0 = skvx::float4::Load(other.fMat + 0); auto b1 = skvx::float4::Load(other.fMat + 4); auto b2 = skvx::float4::Load(other.fMat + 8); auto b3 = skvx::float4::Load(other.fMat + 12); auto eq = (a0 == b0) & (a1 == b1) & (a2 == b2) & (a3 == b3); return (eq[0] & eq[1] & eq[2] & eq[3]) == ~0; } static void transpose_arrays(SkScalar dst[], const SkScalar src[]) { dst[0] = src[0]; dst[1] = src[4]; dst[2] = src[8]; dst[3] = src[12]; dst[4] = src[1]; dst[5] = src[5]; dst[6] = src[9]; dst[7] = src[13]; dst[8] = src[2]; dst[9] = src[6]; dst[10] = src[10]; dst[11] = src[14]; dst[12] = src[3]; dst[13] = src[7]; dst[14] = src[11]; dst[15] = src[15]; } void SkM44::getRowMajor(SkScalar v[]) const { transpose_arrays(v, fMat); } SkM44& SkM44::setConcat(const SkM44& a, const SkM44& b) { auto c0 = skvx::float4::Load(a.fMat + 0); auto c1 = skvx::float4::Load(a.fMat + 4); auto c2 = skvx::float4::Load(a.fMat + 8); auto c3 = skvx::float4::Load(a.fMat + 12); auto compute = [&](skvx::float4 r) { return c0*r[0] + (c1*r[1] + (c2*r[2] + c3*r[3])); }; auto m0 = compute(skvx::float4::Load(b.fMat + 0)); auto m1 = compute(skvx::float4::Load(b.fMat + 4)); auto m2 = compute(skvx::float4::Load(b.fMat + 8)); auto m3 = compute(skvx::float4::Load(b.fMat + 12)); m0.store(fMat + 0); m1.store(fMat + 4); m2.store(fMat + 8); m3.store(fMat + 12); return *this; } SkM44& SkM44::preConcat(const SkMatrix& b) { auto c0 = skvx::float4::Load(fMat + 0); auto c1 = skvx::float4::Load(fMat + 4); auto c3 = skvx::float4::Load(fMat + 12); auto compute = [&](float r0, float r1, float r3) { return (c0*r0 + (c1*r1 + c3*r3)); }; auto m0 = compute(b[0], b[3], b[6]); auto m1 = compute(b[1], b[4], b[7]); auto m3 = compute(b[2], b[5], b[8]); m0.store(fMat + 0); m1.store(fMat + 4); m3.store(fMat + 12); return *this; } SkM44& SkM44::preTranslate(SkScalar x, SkScalar y, SkScalar z) { auto c0 = skvx::float4::Load(fMat + 0); auto c1 = skvx::float4::Load(fMat + 4); auto c2 = skvx::float4::Load(fMat + 8); auto c3 = skvx::float4::Load(fMat + 12); // only need to update the last column (c0*x + (c1*y + (c2*z + c3))).store(fMat + 12); return *this; } SkM44& SkM44::postTranslate(SkScalar x, SkScalar y, SkScalar z) { skvx::float4 t = { x, y, z, 0 }; (t * fMat[ 3] + skvx::float4::Load(fMat + 0)).store(fMat + 0); (t * fMat[ 7] + skvx::float4::Load(fMat + 4)).store(fMat + 4); (t * fMat[11] + skvx::float4::Load(fMat + 8)).store(fMat + 8); (t * fMat[15] + skvx::float4::Load(fMat + 12)).store(fMat + 12); return *this; } SkM44& SkM44::preScale(SkScalar x, SkScalar y) { auto c0 = skvx::float4::Load(fMat + 0); auto c1 = skvx::float4::Load(fMat + 4); (c0 * x).store(fMat + 0); (c1 * y).store(fMat + 4); return *this; } SkM44& SkM44::preScale(SkScalar x, SkScalar y, SkScalar z) { auto c0 = skvx::float4::Load(fMat + 0); auto c1 = skvx::float4::Load(fMat + 4); auto c2 = skvx::float4::Load(fMat + 8); (c0 * x).store(fMat + 0); (c1 * y).store(fMat + 4); (c2 * z).store(fMat + 8); return *this; } SkV4 SkM44::map(float x, float y, float z, float w) const { auto c0 = skvx::float4::Load(fMat + 0); auto c1 = skvx::float4::Load(fMat + 4); auto c2 = skvx::float4::Load(fMat + 8); auto c3 = skvx::float4::Load(fMat + 12); SkV4 v; (c0*x + (c1*y + (c2*z + c3*w))).store(&v.x); return v; } static SkRect map_rect_affine(const SkRect& src, const float mat[16]) { // When multiplied against vectors of the form , 'flip' allows a single min() // to compute both the min and "negated" max between the xy coordinates. Once finished, another // multiplication produces the original max. const skvx::float4 flip{1.f, 1.f, -1.f, -1.f}; // Since z = 0 and it's assumed ther's no perspective, only load the upper 2x2 and (tx,ty) in c3 auto c0 = skvx::shuffle<0,1,0,1>(skvx::float2::Load(mat + 0)) * flip; auto c1 = skvx::shuffle<0,1,0,1>(skvx::float2::Load(mat + 4)) * flip; auto c3 = skvx::shuffle<0,1,0,1>(skvx::float2::Load(mat + 12)); // Compute the min and max of the four transformed corners pre-translation; then translate once // at the end. auto minMax = c3 + flip * min(min(c0 * src.fLeft + c1 * src.fTop, c0 * src.fRight + c1 * src.fTop), min(c0 * src.fLeft + c1 * src.fBottom, c0 * src.fRight + c1 * src.fBottom)); // minMax holds (min x, min y, max x, max y) so can be copied into an SkRect expecting l,t,r,b SkRect r; minMax.store(&r); return r; } static SkRect map_rect_perspective(const SkRect& src, const float mat[16]) { // Like map_rect_affine, z = 0 so we can skip the 3rd column, but we do need to compute w's // for each corner of the src rect. auto c0 = skvx::float4::Load(mat + 0); auto c1 = skvx::float4::Load(mat + 4); auto c3 = skvx::float4::Load(mat + 12); // Unlike map_rect_affine, we do not defer the 4th column since we may need to homogeneous // coordinates to clip against the w=0 plane auto tl = c0 * src.fLeft + c1 * src.fTop + c3; auto tr = c0 * src.fRight + c1 * src.fTop + c3; auto bl = c0 * src.fLeft + c1 * src.fBottom + c3; auto br = c0 * src.fRight + c1 * src.fBottom + c3; // After clipping to w>0 and projecting to 2d, 'project' employs the same negation trick to // compute min and max at the same time. const skvx::float4 flip{1.f, 1.f, -1.f, -1.f}; auto project = [&flip](const skvx::float4& p0, const skvx::float4& p1, const skvx::float4& p2) { float w0 = p0[3]; if (w0 >= SkPathPriv::kW0PlaneDistance) { // Unclipped, just divide by w return flip * skvx::shuffle<0,1,0,1>(p0) / w0; } else { auto clip = [&](const skvx::float4& p) { float w = p[3]; if (w >= SkPathPriv::kW0PlaneDistance) { float t = (SkPathPriv::kW0PlaneDistance - w0) / (w - w0); auto c = (t * skvx::shuffle<0,1>(p) + (1.f - t) * skvx::shuffle<0,1>(p0)) / SkPathPriv::kW0PlaneDistance; return flip * skvx::shuffle<0,1,0,1>(c); } else { return skvx::float4(SK_ScalarInfinity); } }; // Clip both edges leaving p0, and return the min/max of the two clipped points // (since clip returns infinity when both p0 and 2nd vertex have w<0, it'll // automatically be ignored). return min(clip(p1), clip(p2)); } }; // Project all 4 corners, and pass in their adjacent vertices for clipping if it has w < 0, // then accumulate the min and max xy's. auto minMax = flip * min(min(project(tl, tr, bl), project(tr, br, tl)), min(project(br, bl, tr), project(bl, tl, br))); SkRect r; minMax.store(&r); return r; } SkRect SkMatrixPriv::MapRect(const SkM44& m, const SkRect& src) { const bool hasPerspective = m.fMat[3] != 0 || m.fMat[7] != 0 || m.fMat[11] != 0 || m.fMat[15] != 1; if (hasPerspective) { return map_rect_perspective(src, m.fMat); } else { return map_rect_affine(src, m.fMat); } } void SkM44::normalizePerspective() { // If the bottom row of the matrix is [0, 0, 0, not_one], we will treat the matrix as if it // is in perspective, even though it stills behaves like its affine. If we divide everything // by the not_one value, then it will behave the same, but will be treated as affine, // and therefore faster (e.g. clients can forward-difference calculations). if (fMat[15] != 1 && fMat[15] != 0 && fMat[3] == 0 && fMat[7] == 0 && fMat[11] == 0) { double inv = 1.0 / fMat[15]; (skvx::float4::Load(fMat + 0) * inv).store(fMat + 0); (skvx::float4::Load(fMat + 4) * inv).store(fMat + 4); (skvx::float4::Load(fMat + 8) * inv).store(fMat + 8); (skvx::float4::Load(fMat + 12) * inv).store(fMat + 12); fMat[15] = 1.0f; } } /////////////////////////////////////////////////////////////////////////////// /** We always perform the calculation in doubles, to avoid prematurely losing precision along the way. This relies on the compiler automatically promoting our SkScalar values to double (if needed). */ bool SkM44::invert(SkM44* inverse) const { SkScalar tmp[16]; if (SkInvert4x4Matrix(fMat, tmp) == 0.0f) { return false; } memcpy(inverse->fMat, tmp, sizeof(tmp)); return true; } SkM44 SkM44::transpose() const { SkM44 trans(SkM44::kUninitialized_Constructor); transpose_arrays(trans.fMat, fMat); return trans; } SkM44& SkM44::setRotateUnitSinCos(SkV3 axis, SkScalar sinAngle, SkScalar cosAngle) { // Taken from "Essential Mathematics for Games and Interactive Applications" // James M. Van Verth and Lars M. Bishop -- third edition SkScalar x = axis.x; SkScalar y = axis.y; SkScalar z = axis.z; SkScalar c = cosAngle; SkScalar s = sinAngle; SkScalar t = 1 - c; *this = { t*x*x + c, t*x*y - s*z, t*x*z + s*y, 0, t*x*y + s*z, t*y*y + c, t*y*z - s*x, 0, t*x*z - s*y, t*y*z + s*x, t*z*z + c, 0, 0, 0, 0, 1 }; return *this; } SkM44& SkM44::setRotate(SkV3 axis, SkScalar radians) { SkScalar len = axis.length(); if (len > 0 && SkScalarIsFinite(len)) { this->setRotateUnit(axis * (SK_Scalar1 / len), radians); } else { this->setIdentity(); } return *this; } /////////////////////////////////////////////////////////////////////////////// void SkM44::dump() const { SkDebugf("|%g %g %g %g|\n" "|%g %g %g %g|\n" "|%g %g %g %g|\n" "|%g %g %g %g|\n", fMat[0], fMat[4], fMat[8], fMat[12], fMat[1], fMat[5], fMat[9], fMat[13], fMat[2], fMat[6], fMat[10], fMat[14], fMat[3], fMat[7], fMat[11], fMat[15]); } /////////////////////////////////////////////////////////////////////////////// SkM44 SkM44::RectToRect(const SkRect& src, const SkRect& dst) { if (src.isEmpty()) { return SkM44(); } else if (dst.isEmpty()) { return SkM44::Scale(0.f, 0.f, 0.f); } float sx = dst.width() / src.width(); float sy = dst.height() / src.height(); float tx = dst.fLeft - sx * src.fLeft; float ty = dst.fTop - sy * src.fTop; return SkM44{sx, 0.f, 0.f, tx, 0.f, sy, 0.f, ty, 0.f, 0.f, 1.f, 0.f, 0.f, 0.f, 0.f, 1.f}; } static SkV3 normalize(SkV3 v) { const auto vlen = v.length(); return SkScalarNearlyZero(vlen) ? v : v * (1.0f / vlen); } static SkV4 v4(SkV3 v, SkScalar w) { return {v.x, v.y, v.z, w}; } SkM44 SkM44::LookAt(const SkV3& eye, const SkV3& center, const SkV3& up) { SkV3 f = normalize(center - eye); SkV3 u = normalize(up); SkV3 s = normalize(f.cross(u)); SkM44 m(SkM44::kUninitialized_Constructor); if (!SkM44::Cols(v4(s, 0), v4(s.cross(f), 0), v4(-f, 0), v4(eye, 1)).invert(&m)) { m.setIdentity(); } return m; } SkM44 SkM44::Perspective(float near, float far, float angle) { SkASSERT(far > near); float denomInv = sk_ieee_float_divide(1, far - near); float halfAngle = angle * 0.5f; SkASSERT(halfAngle != 0); float cot = sk_ieee_float_divide(1, sk_float_tan(halfAngle)); SkM44 m; m.setRC(0, 0, cot); m.setRC(1, 1, cot); m.setRC(2, 2, (far + near) * denomInv); m.setRC(2, 3, 2 * far * near * denomInv); m.setRC(3, 2, -1); return m; } struct SkSamplingOptions; void SkMatrix::doNormalizePerspective() { // If the bottom row of the matrix is [0, 0, not_one], we will treat the matrix as if it // is in perspective, even though it stills behaves like its affine. If we divide everything // by the not_one value, then it will behave the same, but will be treated as affine, // and therefore faster (e.g. clients can forward-difference calculations). // if (0 == fMat[SkMatrix::kMPersp0] && 0 == fMat[SkMatrix::kMPersp1]) { SkScalar p2 = fMat[SkMatrix::kMPersp2]; if (p2 != 0 && p2 != 1) { double inv = 1.0 / p2; for (int i = 0; i < 6; ++i) { fMat[i] = SkDoubleToScalar(fMat[i] * inv); } fMat[SkMatrix::kMPersp2] = 1; } this->setTypeMask(kUnknown_Mask); } } SkMatrix& SkMatrix::reset() { *this = SkMatrix(); return *this; } SkMatrix& SkMatrix::set9(const SkScalar buffer[9]) { memcpy(fMat, buffer, 9 * sizeof(SkScalar)); this->setTypeMask(kUnknown_Mask); return *this; } SkMatrix& SkMatrix::setAffine(const SkScalar buffer[6]) { fMat[kMScaleX] = buffer[kAScaleX]; fMat[kMSkewX] = buffer[kASkewX]; fMat[kMTransX] = buffer[kATransX]; fMat[kMSkewY] = buffer[kASkewY]; fMat[kMScaleY] = buffer[kAScaleY]; fMat[kMTransY] = buffer[kATransY]; fMat[kMPersp0] = 0; fMat[kMPersp1] = 0; fMat[kMPersp2] = 1; this->setTypeMask(kUnknown_Mask); return *this; } // this aligns with the masks, so we can compute a mask from a variable 0/1 enum { kTranslate_Shift, kScale_Shift, kAffine_Shift, kPerspective_Shift, kRectStaysRect_Shift }; static const int32_t kScalar1Int = 0x3f800000; uint8_t SkMatrix::computePerspectiveTypeMask() const { // Benchmarking suggests that replacing this set of SkScalarAs2sCompliment // is a win, but replacing those below is not. We don't yet understand // that result. if (fMat[kMPersp0] != 0 || fMat[kMPersp1] != 0 || fMat[kMPersp2] != 1) { // If this is a perspective transform, we return true for all other // transform flags - this does not disable any optimizations, respects // the rule that the type mask must be conservative, and speeds up // type mask computation. return SkToU8(kORableMasks); } return SkToU8(kOnlyPerspectiveValid_Mask | kUnknown_Mask); } uint8_t SkMatrix::computeTypeMask() const { unsigned mask = 0; if (fMat[kMPersp0] != 0 || fMat[kMPersp1] != 0 || fMat[kMPersp2] != 1) { // Once it is determined that that this is a perspective transform, // all other flags are moot as far as optimizations are concerned. return SkToU8(kORableMasks); } if (fMat[kMTransX] != 0 || fMat[kMTransY] != 0) { mask |= kTranslate_Mask; } int m00 = SkScalarAs2sCompliment(fMat[SkMatrix::kMScaleX]); int m01 = SkScalarAs2sCompliment(fMat[SkMatrix::kMSkewX]); int m10 = SkScalarAs2sCompliment(fMat[SkMatrix::kMSkewY]); int m11 = SkScalarAs2sCompliment(fMat[SkMatrix::kMScaleY]); if (m01 | m10) { // The skew components may be scale-inducing, unless we are dealing // with a pure rotation. Testing for a pure rotation is expensive, // so we opt for being conservative by always setting the scale bit. // along with affine. // By doing this, we are also ensuring that matrices have the same // type masks as their inverses. mask |= kAffine_Mask | kScale_Mask; // For rectStaysRect, in the affine case, we only need check that // the primary diagonal is all zeros and that the secondary diagonal // is all non-zero. // map non-zero to 1 m01 = m01 != 0; m10 = m10 != 0; int dp0 = 0 == (m00 | m11) ; // true if both are 0 int ds1 = m01 & m10; // true if both are 1 mask |= (dp0 & ds1) << kRectStaysRect_Shift; } else { // Only test for scale explicitly if not affine, since affine sets the // scale bit. if ((m00 ^ kScalar1Int) | (m11 ^ kScalar1Int)) { mask |= kScale_Mask; } // Not affine, therefore we already know secondary diagonal is // all zeros, so we just need to check that primary diagonal is // all non-zero. // map non-zero to 1 m00 = m00 != 0; m11 = m11 != 0; // record if the (p)rimary diagonal is all non-zero mask |= (m00 & m11) << kRectStaysRect_Shift; } return SkToU8(mask); } /////////////////////////////////////////////////////////////////////////////// bool operator==(const SkMatrix& a, const SkMatrix& b) { const SkScalar* SK_RESTRICT ma = a.fMat; const SkScalar* SK_RESTRICT mb = b.fMat; return ma[0] == mb[0] && ma[1] == mb[1] && ma[2] == mb[2] && ma[3] == mb[3] && ma[4] == mb[4] && ma[5] == mb[5] && ma[6] == mb[6] && ma[7] == mb[7] && ma[8] == mb[8]; } /////////////////////////////////////////////////////////////////////////////// // helper function to determine if upper-left 2x2 of matrix is degenerate static inline bool is_degenerate_2x2(SkScalar scaleX, SkScalar skewX, SkScalar skewY, SkScalar scaleY) { SkScalar perp_dot = scaleX*scaleY - skewX*skewY; return SkScalarNearlyZero(perp_dot, SK_ScalarNearlyZero*SK_ScalarNearlyZero); } /////////////////////////////////////////////////////////////////////////////// bool SkMatrix::isSimilarity(SkScalar tol) const { // if identity or translate matrix TypeMask mask = this->getType(); if (mask <= kTranslate_Mask) { return true; } if (mask & kPerspective_Mask) { return false; } SkScalar mx = fMat[kMScaleX]; SkScalar my = fMat[kMScaleY]; // if no skew, can just compare scale factors if (!(mask & kAffine_Mask)) { return !SkScalarNearlyZero(mx) && SkScalarNearlyEqual(SkScalarAbs(mx), SkScalarAbs(my)); } SkScalar sx = fMat[kMSkewX]; SkScalar sy = fMat[kMSkewY]; if (is_degenerate_2x2(mx, sx, sy, my)) { return false; } // upper 2x2 is rotation/reflection + uniform scale if basis vectors // are 90 degree rotations of each other return (SkScalarNearlyEqual(mx, my, tol) && SkScalarNearlyEqual(sx, -sy, tol)) || (SkScalarNearlyEqual(mx, -my, tol) && SkScalarNearlyEqual(sx, sy, tol)); } bool SkMatrix::preservesRightAngles(SkScalar tol) const { TypeMask mask = this->getType(); if (mask <= kTranslate_Mask) { // identity, translate and/or scale return true; } if (mask & kPerspective_Mask) { return false; } SkASSERT(mask & (kAffine_Mask | kScale_Mask)); SkScalar mx = fMat[kMScaleX]; SkScalar my = fMat[kMScaleY]; SkScalar sx = fMat[kMSkewX]; SkScalar sy = fMat[kMSkewY]; if (is_degenerate_2x2(mx, sx, sy, my)) { return false; } // upper 2x2 is scale + rotation/reflection if basis vectors are orthogonal SkVector vec[2]; vec[0].set(mx, sy); vec[1].set(sx, my); return SkScalarNearlyZero(vec[0].dot(vec[1]), SkScalarSquare(tol)); } /////////////////////////////////////////////////////////////////////////////// static inline SkScalar sdot(SkScalar a, SkScalar b, SkScalar c, SkScalar d) { return a * b + c * d; } static inline SkScalar sdot(SkScalar a, SkScalar b, SkScalar c, SkScalar d, SkScalar e, SkScalar f) { return a * b + c * d + e * f; } static inline SkScalar scross(SkScalar a, SkScalar b, SkScalar c, SkScalar d) { return a * b - c * d; } SkMatrix& SkMatrix::setTranslate(SkScalar dx, SkScalar dy) { *this = SkMatrix(1, 0, dx, 0, 1, dy, 0, 0, 1, (dx != 0 || dy != 0) ? kTranslate_Mask | kRectStaysRect_Mask : kIdentity_Mask | kRectStaysRect_Mask); return *this; } SkMatrix& SkMatrix::preTranslate(SkScalar dx, SkScalar dy) { const unsigned mask = this->getType(); if (mask <= kTranslate_Mask) { fMat[kMTransX] += dx; fMat[kMTransY] += dy; } else if (mask & kPerspective_Mask) { SkMatrix m; m.setTranslate(dx, dy); return this->preConcat(m); } else { fMat[kMTransX] += sdot(fMat[kMScaleX], dx, fMat[kMSkewX], dy); fMat[kMTransY] += sdot(fMat[kMSkewY], dx, fMat[kMScaleY], dy); } this->updateTranslateMask(); return *this; } SkMatrix& SkMatrix::postTranslate(SkScalar dx, SkScalar dy) { if (this->hasPerspective()) { SkMatrix m; m.setTranslate(dx, dy); this->postConcat(m); } else { fMat[kMTransX] += dx; fMat[kMTransY] += dy; this->updateTranslateMask(); } return *this; } /////////////////////////////////////////////////////////////////////////////// SkMatrix& SkMatrix::setScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { if (1 == sx && 1 == sy) { this->reset(); } else { this->setScaleTranslate(sx, sy, px - sx * px, py - sy * py); } return *this; } SkMatrix& SkMatrix::setScale(SkScalar sx, SkScalar sy) { auto rectMask = (sx == 0 || sy == 0) ? 0 : kRectStaysRect_Mask; *this = SkMatrix(sx, 0, 0, 0, sy, 0, 0, 0, 1, (sx == 1 && sy == 1) ? kIdentity_Mask | rectMask : kScale_Mask | rectMask); return *this; } SkMatrix& SkMatrix::preScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { if (1 == sx && 1 == sy) { return *this; } SkMatrix m; m.setScale(sx, sy, px, py); return this->preConcat(m); } SkMatrix& SkMatrix::preScale(SkScalar sx, SkScalar sy) { if (1 == sx && 1 == sy) { return *this; } // the assumption is that these multiplies are very cheap, and that // a full concat and/or just computing the matrix type is more expensive. // Also, the fixed-point case checks for overflow, but the float doesn't, // so we can get away with these blind multiplies. fMat[kMScaleX] *= sx; fMat[kMSkewY] *= sx; fMat[kMPersp0] *= sx; fMat[kMSkewX] *= sy; fMat[kMScaleY] *= sy; fMat[kMPersp1] *= sy; // Attempt to simplify our type when applying an inverse scale. // TODO: The persp/affine preconditions are in place to keep the mask consistent with // what computeTypeMask() would produce (persp/skew always implies kScale). // We should investigate whether these flag dependencies are truly needed. if (fMat[kMScaleX] == 1 && fMat[kMScaleY] == 1 && !(fTypeMask & (kPerspective_Mask | kAffine_Mask))) { this->clearTypeMask(kScale_Mask); } else { this->orTypeMask(kScale_Mask); // Remove kRectStaysRect if the preScale factors were 0 if (!sx || !sy) { this->clearTypeMask(kRectStaysRect_Mask); } } return *this; } SkMatrix& SkMatrix::postScale(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { if (1 == sx && 1 == sy) { return *this; } SkMatrix m; m.setScale(sx, sy, px, py); return this->postConcat(m); } SkMatrix& SkMatrix::postScale(SkScalar sx, SkScalar sy) { if (1 == sx && 1 == sy) { return *this; } SkMatrix m; m.setScale(sx, sy); return this->postConcat(m); } // this perhaps can go away, if we have a fract/high-precision way to // scale matrices bool SkMatrix::postIDiv(int divx, int divy) { if (divx == 0 || divy == 0) { return false; } const float invX = 1.f / divx; const float invY = 1.f / divy; fMat[kMScaleX] *= invX; fMat[kMSkewX] *= invX; fMat[kMTransX] *= invX; fMat[kMScaleY] *= invY; fMat[kMSkewY] *= invY; fMat[kMTransY] *= invY; this->setTypeMask(kUnknown_Mask); return true; } //////////////////////////////////////////////////////////////////////////////////// SkMatrix& SkMatrix::setSinCos(SkScalar sinV, SkScalar cosV, SkScalar px, SkScalar py) { const SkScalar oneMinusCosV = 1 - cosV; fMat[kMScaleX] = cosV; fMat[kMSkewX] = -sinV; fMat[kMTransX] = sdot(sinV, py, oneMinusCosV, px); fMat[kMSkewY] = sinV; fMat[kMScaleY] = cosV; fMat[kMTransY] = sdot(-sinV, px, oneMinusCosV, py); fMat[kMPersp0] = fMat[kMPersp1] = 0; fMat[kMPersp2] = 1; this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); return *this; } SkMatrix& SkMatrix::setRSXform(const SkRSXform& xform) { fMat[kMScaleX] = xform.fSCos; fMat[kMSkewX] = -xform.fSSin; fMat[kMTransX] = xform.fTx; fMat[kMSkewY] = xform.fSSin; fMat[kMScaleY] = xform.fSCos; fMat[kMTransY] = xform.fTy; fMat[kMPersp0] = fMat[kMPersp1] = 0; fMat[kMPersp2] = 1; this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); return *this; } SkMatrix& SkMatrix::setSinCos(SkScalar sinV, SkScalar cosV) { fMat[kMScaleX] = cosV; fMat[kMSkewX] = -sinV; fMat[kMTransX] = 0; fMat[kMSkewY] = sinV; fMat[kMScaleY] = cosV; fMat[kMTransY] = 0; fMat[kMPersp0] = fMat[kMPersp1] = 0; fMat[kMPersp2] = 1; this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); return *this; } SkMatrix& SkMatrix::setRotate(SkScalar degrees, SkScalar px, SkScalar py) { SkScalar rad = SkDegreesToRadians(degrees); return this->setSinCos(SkScalarSinSnapToZero(rad), SkScalarCosSnapToZero(rad), px, py); } SkMatrix& SkMatrix::setRotate(SkScalar degrees) { SkScalar rad = SkDegreesToRadians(degrees); return this->setSinCos(SkScalarSinSnapToZero(rad), SkScalarCosSnapToZero(rad)); } SkMatrix& SkMatrix::preRotate(SkScalar degrees, SkScalar px, SkScalar py) { SkMatrix m; m.setRotate(degrees, px, py); return this->preConcat(m); } SkMatrix& SkMatrix::preRotate(SkScalar degrees) { SkMatrix m; m.setRotate(degrees); return this->preConcat(m); } SkMatrix& SkMatrix::postRotate(SkScalar degrees, SkScalar px, SkScalar py) { SkMatrix m; m.setRotate(degrees, px, py); return this->postConcat(m); } SkMatrix& SkMatrix::postRotate(SkScalar degrees) { SkMatrix m; m.setRotate(degrees); return this->postConcat(m); } //////////////////////////////////////////////////////////////////////////////////// SkMatrix& SkMatrix::setSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { *this = SkMatrix(1, sx, -sx * py, sy, 1, -sy * px, 0, 0, 1, kUnknown_Mask | kOnlyPerspectiveValid_Mask); return *this; } SkMatrix& SkMatrix::setSkew(SkScalar sx, SkScalar sy) { fMat[kMScaleX] = 1; fMat[kMSkewX] = sx; fMat[kMTransX] = 0; fMat[kMSkewY] = sy; fMat[kMScaleY] = 1; fMat[kMTransY] = 0; fMat[kMPersp0] = fMat[kMPersp1] = 0; fMat[kMPersp2] = 1; this->setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); return *this; } SkMatrix& SkMatrix::preSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { SkMatrix m; m.setSkew(sx, sy, px, py); return this->preConcat(m); } SkMatrix& SkMatrix::preSkew(SkScalar sx, SkScalar sy) { SkMatrix m; m.setSkew(sx, sy); return this->preConcat(m); } SkMatrix& SkMatrix::postSkew(SkScalar sx, SkScalar sy, SkScalar px, SkScalar py) { SkMatrix m; m.setSkew(sx, sy, px, py); return this->postConcat(m); } SkMatrix& SkMatrix::postSkew(SkScalar sx, SkScalar sy) { SkMatrix m; m.setSkew(sx, sy); return this->postConcat(m); } /////////////////////////////////////////////////////////////////////////////// bool SkMatrix::setRectToRect(const SkRect& src, const SkRect& dst, ScaleToFit align) { if (src.isEmpty()) { this->reset(); return false; } if (dst.isEmpty()) { sk_bzero(fMat, 8 * sizeof(SkScalar)); fMat[kMPersp2] = 1; this->setTypeMask(kScale_Mask); } else { SkScalar tx, sx = dst.width() / src.width(); SkScalar ty, sy = dst.height() / src.height(); bool xLarger = false; if (align != kFill_ScaleToFit) { if (sx > sy) { xLarger = true; sx = sy; } else { sy = sx; } } tx = dst.fLeft - src.fLeft * sx; ty = dst.fTop - src.fTop * sy; if (align == kCenter_ScaleToFit || align == kEnd_ScaleToFit) { SkScalar diff; if (xLarger) { diff = dst.width() - src.width() * sy; } else { diff = dst.height() - src.height() * sy; } if (align == kCenter_ScaleToFit) { diff = SkScalarHalf(diff); } if (xLarger) { tx += diff; } else { ty += diff; } } this->setScaleTranslate(sx, sy, tx, ty); } return true; } /////////////////////////////////////////////////////////////////////////////// static inline float muladdmul(float a, float b, float c, float d) { return sk_double_to_float((double)a * b + (double)c * d); } static inline float rowcol3(const float row[], const float col[]) { return row[0] * col[0] + row[1] * col[3] + row[2] * col[6]; } static bool only_scale_and_translate(unsigned mask) { return 0 == (mask & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask)); } SkMatrix& SkMatrix::setConcat(const SkMatrix& a, const SkMatrix& b) { TypeMask aType = a.getType(); TypeMask bType = b.getType(); if (a.isTriviallyIdentity()) { *this = b; } else if (b.isTriviallyIdentity()) { *this = a; } else if (only_scale_and_translate(aType | bType)) { this->setScaleTranslate(a.fMat[kMScaleX] * b.fMat[kMScaleX], a.fMat[kMScaleY] * b.fMat[kMScaleY], a.fMat[kMScaleX] * b.fMat[kMTransX] + a.fMat[kMTransX], a.fMat[kMScaleY] * b.fMat[kMTransY] + a.fMat[kMTransY]); } else { SkMatrix tmp; if ((aType | bType) & kPerspective_Mask) { tmp.fMat[kMScaleX] = rowcol3(&a.fMat[0], &b.fMat[0]); tmp.fMat[kMSkewX] = rowcol3(&a.fMat[0], &b.fMat[1]); tmp.fMat[kMTransX] = rowcol3(&a.fMat[0], &b.fMat[2]); tmp.fMat[kMSkewY] = rowcol3(&a.fMat[3], &b.fMat[0]); tmp.fMat[kMScaleY] = rowcol3(&a.fMat[3], &b.fMat[1]); tmp.fMat[kMTransY] = rowcol3(&a.fMat[3], &b.fMat[2]); tmp.fMat[kMPersp0] = rowcol3(&a.fMat[6], &b.fMat[0]); tmp.fMat[kMPersp1] = rowcol3(&a.fMat[6], &b.fMat[1]); tmp.fMat[kMPersp2] = rowcol3(&a.fMat[6], &b.fMat[2]); tmp.setTypeMask(kUnknown_Mask); } else { tmp.fMat[kMScaleX] = muladdmul(a.fMat[kMScaleX], b.fMat[kMScaleX], a.fMat[kMSkewX], b.fMat[kMSkewY]); tmp.fMat[kMSkewX] = muladdmul(a.fMat[kMScaleX], b.fMat[kMSkewX], a.fMat[kMSkewX], b.fMat[kMScaleY]); tmp.fMat[kMTransX] = muladdmul(a.fMat[kMScaleX], b.fMat[kMTransX], a.fMat[kMSkewX], b.fMat[kMTransY]) + a.fMat[kMTransX]; tmp.fMat[kMSkewY] = muladdmul(a.fMat[kMSkewY], b.fMat[kMScaleX], a.fMat[kMScaleY], b.fMat[kMSkewY]); tmp.fMat[kMScaleY] = muladdmul(a.fMat[kMSkewY], b.fMat[kMSkewX], a.fMat[kMScaleY], b.fMat[kMScaleY]); tmp.fMat[kMTransY] = muladdmul(a.fMat[kMSkewY], b.fMat[kMTransX], a.fMat[kMScaleY], b.fMat[kMTransY]) + a.fMat[kMTransY]; tmp.fMat[kMPersp0] = 0; tmp.fMat[kMPersp1] = 0; tmp.fMat[kMPersp2] = 1; //SkDebugf("Concat mat non-persp type: %d\n", tmp.getType()); //SkASSERT(!(tmp.getType() & kPerspective_Mask)); tmp.setTypeMask(kUnknown_Mask | kOnlyPerspectiveValid_Mask); } *this = tmp; } return *this; } SkMatrix& SkMatrix::preConcat(const SkMatrix& mat) { // check for identity first, so we don't do a needless copy of ourselves // to ourselves inside setConcat() if(!mat.isIdentity()) { this->setConcat(*this, mat); } return *this; } SkMatrix& SkMatrix::postConcat(const SkMatrix& mat) { // check for identity first, so we don't do a needless copy of ourselves // to ourselves inside setConcat() if (!mat.isIdentity()) { this->setConcat(mat, *this); } return *this; } /////////////////////////////////////////////////////////////////////////////// /* Matrix inversion is very expensive, but also the place where keeping precision may be most important (here and matrix concat). Hence to avoid bitmap blitting artifacts when walking the inverse, we use doubles for the intermediate math, even though we know that is more expensive. */ static inline SkScalar scross_dscale(SkScalar a, SkScalar b, SkScalar c, SkScalar d, double scale) { return SkDoubleToScalar(scross(a, b, c, d) * scale); } static inline double dcross(double a, double b, double c, double d) { return a * b - c * d; } static inline SkScalar dcross_dscale(double a, double b, double c, double d, double scale) { return SkDoubleToScalar(dcross(a, b, c, d) * scale); } static double sk_determinant(const float mat[9], int isPerspective) { if (isPerspective) { return mat[SkMatrix::kMScaleX] * dcross(mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp2], mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp1]) + mat[SkMatrix::kMSkewX] * dcross(mat[SkMatrix::kMTransY], mat[SkMatrix::kMPersp0], mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp2]) + mat[SkMatrix::kMTransX] * dcross(mat[SkMatrix::kMSkewY], mat[SkMatrix::kMPersp1], mat[SkMatrix::kMScaleY], mat[SkMatrix::kMPersp0]); } else { return dcross(mat[SkMatrix::kMScaleX], mat[SkMatrix::kMScaleY], mat[SkMatrix::kMSkewX], mat[SkMatrix::kMSkewY]); } } static double sk_inv_determinant(const float mat[9], int isPerspective) { double det = sk_determinant(mat, isPerspective); // Since the determinant is on the order of the cube of the matrix members, // compare to the cube of the default nearly-zero constant (although an // estimate of the condition number would be better if it wasn't so expensive). if (SkScalarNearlyZero(sk_double_to_float(det), SK_ScalarNearlyZero * SK_ScalarNearlyZero * SK_ScalarNearlyZero)) { return 0; } return 1.0 / det; } void SkMatrix::SetAffineIdentity(SkScalar affine[6]) { affine[kAScaleX] = 1; affine[kASkewY] = 0; affine[kASkewX] = 0; affine[kAScaleY] = 1; affine[kATransX] = 0; affine[kATransY] = 0; } bool SkMatrix::asAffine(SkScalar affine[6]) const { if (this->hasPerspective()) { return false; } if (affine) { affine[kAScaleX] = this->fMat[kMScaleX]; affine[kASkewY] = this->fMat[kMSkewY]; affine[kASkewX] = this->fMat[kMSkewX]; affine[kAScaleY] = this->fMat[kMScaleY]; affine[kATransX] = this->fMat[kMTransX]; affine[kATransY] = this->fMat[kMTransY]; } return true; } void SkMatrix::mapPoints(SkPoint dst[], const SkPoint src[], int count) const { SkASSERT((dst && src && count > 0) || 0 == count); // no partial overlap SkASSERT(src == dst || &dst[count] <= &src[0] || &src[count] <= &dst[0]); this->getMapPtsProc()(*this, dst, src, count); } void SkMatrix::mapXY(SkScalar x, SkScalar y, SkPoint* result) const { SkASSERT(result); this->getMapXYProc()(*this, x, y, result); } void SkMatrix::ComputeInv(SkScalar dst[9], const SkScalar src[9], double invDet, bool isPersp) { SkASSERT(src != dst); SkASSERT(src && dst); if (isPersp) { dst[kMScaleX] = scross_dscale(src[kMScaleY], src[kMPersp2], src[kMTransY], src[kMPersp1], invDet); dst[kMSkewX] = scross_dscale(src[kMTransX], src[kMPersp1], src[kMSkewX], src[kMPersp2], invDet); dst[kMTransX] = scross_dscale(src[kMSkewX], src[kMTransY], src[kMTransX], src[kMScaleY], invDet); dst[kMSkewY] = scross_dscale(src[kMTransY], src[kMPersp0], src[kMSkewY], src[kMPersp2], invDet); dst[kMScaleY] = scross_dscale(src[kMScaleX], src[kMPersp2], src[kMTransX], src[kMPersp0], invDet); dst[kMTransY] = scross_dscale(src[kMTransX], src[kMSkewY], src[kMScaleX], src[kMTransY], invDet); dst[kMPersp0] = scross_dscale(src[kMSkewY], src[kMPersp1], src[kMScaleY], src[kMPersp0], invDet); dst[kMPersp1] = scross_dscale(src[kMSkewX], src[kMPersp0], src[kMScaleX], src[kMPersp1], invDet); dst[kMPersp2] = scross_dscale(src[kMScaleX], src[kMScaleY], src[kMSkewX], src[kMSkewY], invDet); } else { // not perspective dst[kMScaleX] = SkDoubleToScalar(src[kMScaleY] * invDet); dst[kMSkewX] = SkDoubleToScalar(-src[kMSkewX] * invDet); dst[kMTransX] = dcross_dscale(src[kMSkewX], src[kMTransY], src[kMScaleY], src[kMTransX], invDet); dst[kMSkewY] = SkDoubleToScalar(-src[kMSkewY] * invDet); dst[kMScaleY] = SkDoubleToScalar(src[kMScaleX] * invDet); dst[kMTransY] = dcross_dscale(src[kMSkewY], src[kMTransX], src[kMScaleX], src[kMTransY], invDet); dst[kMPersp0] = 0; dst[kMPersp1] = 0; dst[kMPersp2] = 1; } } bool SkMatrix::invertNonIdentity(SkMatrix* inv) const { SkASSERT(!this->isIdentity()); TypeMask mask = this->getType(); if (0 == (mask & ~(kScale_Mask | kTranslate_Mask))) { bool invertible = true; if (inv) { if (mask & kScale_Mask) { SkScalar invX = sk_ieee_float_divide(1.f, fMat[kMScaleX]); SkScalar invY = sk_ieee_float_divide(1.f, fMat[kMScaleY]); // Denormalized (non-zero) scale factors will overflow when inverted, in which case // the inverse matrix would not be finite, so return false. if (!SkScalarsAreFinite(invX, invY)) { return false; } // Must be careful when writing to inv, since it may be the // same memory as this. inv->fMat[kMSkewX] = inv->fMat[kMSkewY] = inv->fMat[kMPersp0] = inv->fMat[kMPersp1] = 0; inv->fMat[kMScaleX] = invX; inv->fMat[kMScaleY] = invY; inv->fMat[kMPersp2] = 1; inv->fMat[kMTransX] = -fMat[kMTransX] * invX; inv->fMat[kMTransY] = -fMat[kMTransY] * invY; inv->setTypeMask(mask | kRectStaysRect_Mask); } else { // translate only inv->setTranslate(-fMat[kMTransX], -fMat[kMTransY]); } } else { // inv is nullptr, just check if we're invertible if (!fMat[kMScaleX] || !fMat[kMScaleY]) { invertible = false; } } return invertible; } int isPersp = mask & kPerspective_Mask; double invDet = sk_inv_determinant(fMat, isPersp); if (invDet == 0) { // underflow return false; } bool applyingInPlace = (inv == this); SkMatrix* tmp = inv; SkMatrix storage; if (applyingInPlace || nullptr == tmp) { tmp = &storage; // we either need to avoid trampling memory or have no memory } ComputeInv(tmp->fMat, fMat, invDet, isPersp); if (!tmp->isFinite()) { return false; } tmp->setTypeMask(fTypeMask); if (applyingInPlace) { *inv = storage; // need to copy answer back } return true; } /////////////////////////////////////////////////////////////////////////////// void SkMatrix::Identity_pts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { SkASSERT(m.getType() == 0); if (dst != src && count > 0) { memcpy(dst, src, count * sizeof(SkPoint)); } } void SkMatrix::Trans_pts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { SkASSERT(m.getType() <= SkMatrix::kTranslate_Mask); if (count > 0) { SkScalar tx = m.getTranslateX(); SkScalar ty = m.getTranslateY(); if (count & 1) { dst->fX = src->fX + tx; dst->fY = src->fY + ty; src += 1; dst += 1; } skvx::float4 trans4(tx, ty, tx, ty); count >>= 1; if (count & 1) { (skvx::float4::Load(src) + trans4).store(dst); src += 2; dst += 2; } count >>= 1; for (int i = 0; i < count; ++i) { (skvx::float4::Load(src+0) + trans4).store(dst+0); (skvx::float4::Load(src+2) + trans4).store(dst+2); src += 4; dst += 4; } } } void SkMatrix::Scale_pts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { SkASSERT(m.getType() <= (SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)); if (count > 0) { SkScalar tx = m.getTranslateX(); SkScalar ty = m.getTranslateY(); SkScalar sx = m.getScaleX(); SkScalar sy = m.getScaleY(); skvx::float4 trans4(tx, ty, tx, ty); skvx::float4 scale4(sx, sy, sx, sy); if (count & 1) { skvx::float4 p(src->fX, src->fY, 0, 0); p = p * scale4 + trans4; dst->fX = p[0]; dst->fY = p[1]; src += 1; dst += 1; } count >>= 1; if (count & 1) { (skvx::float4::Load(src) * scale4 + trans4).store(dst); src += 2; dst += 2; } count >>= 1; for (int i = 0; i < count; ++i) { (skvx::float4::Load(src+0) * scale4 + trans4).store(dst+0); (skvx::float4::Load(src+2) * scale4 + trans4).store(dst+2); src += 4; dst += 4; } } } void SkMatrix::Persp_pts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { SkASSERT(m.hasPerspective()); if (count > 0) { do { SkScalar sy = src->fY; SkScalar sx = src->fX; src += 1; SkScalar x = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; SkScalar y = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; SkScalar z = sdot(sx, m.fMat[kMPersp0], sy, m.fMat[kMPersp1]) + m.fMat[kMPersp2]; if (z) { z = 1 / z; } dst->fY = y * z; dst->fX = x * z; dst += 1; } while (--count); } } void SkMatrix::Affine_vpts(const SkMatrix& m, SkPoint dst[], const SkPoint src[], int count) { SkASSERT(m.getType() != SkMatrix::kPerspective_Mask); if (count > 0) { SkScalar tx = m.getTranslateX(); SkScalar ty = m.getTranslateY(); SkScalar sx = m.getScaleX(); SkScalar sy = m.getScaleY(); SkScalar kx = m.getSkewX(); SkScalar ky = m.getSkewY(); skvx::float4 trans4(tx, ty, tx, ty); skvx::float4 scale4(sx, sy, sx, sy); skvx::float4 skew4(kx, ky, kx, ky); // applied to swizzle of src4 bool trailingElement = (count & 1); count >>= 1; skvx::float4 src4; for (int i = 0; i < count; ++i) { src4 = skvx::float4::Load(src); skvx::float4 swz4 = skvx::shuffle<1,0,3,2>(src4); // y0 x0, y1 x1 (src4 * scale4 + swz4 * skew4 + trans4).store(dst); src += 2; dst += 2; } if (trailingElement) { // We use the same logic here to ensure that the math stays consistent throughout, even // though the high float2 is ignored. src4.lo = skvx::float2::Load(src); skvx::float4 swz4 = skvx::shuffle<1,0,3,2>(src4); // y0 x0, y1 x1 (src4 * scale4 + swz4 * skew4 + trans4).lo.store(dst); } } } const SkMatrix::MapPtsProc SkMatrix::gMapPtsProcs[] = { SkMatrix::Identity_pts, SkMatrix::Trans_pts, SkMatrix::Scale_pts, SkMatrix::Scale_pts, SkMatrix::Affine_vpts, SkMatrix::Affine_vpts, SkMatrix::Affine_vpts, SkMatrix::Affine_vpts, // repeat the persp proc 8 times SkMatrix::Persp_pts, SkMatrix::Persp_pts, SkMatrix::Persp_pts, SkMatrix::Persp_pts, SkMatrix::Persp_pts, SkMatrix::Persp_pts, SkMatrix::Persp_pts, SkMatrix::Persp_pts }; /////////////////////////////////////////////////////////////////////////////// void SkMatrixPriv::MapHomogeneousPointsWithStride(const SkMatrix& mx, SkPoint3 dst[], size_t dstStride, const SkPoint3 src[], size_t srcStride, int count) { SkASSERT((dst && src && count > 0) || 0 == count); // no partial overlap SkASSERT(src == dst || &dst[count] <= &src[0] || &src[count] <= &dst[0]); if (count > 0) { if (mx.isIdentity()) { if (src != dst) { if (srcStride == sizeof(SkPoint3) && dstStride == sizeof(SkPoint3)) { memcpy(dst, src, count * sizeof(SkPoint3)); } else { for (int i = 0; i < count; ++i) { *dst = *src; dst = reinterpret_cast(reinterpret_cast(dst) + dstStride); src = reinterpret_cast(reinterpret_cast(src) + srcStride); } } } return; } do { SkScalar sx = src->fX; SkScalar sy = src->fY; SkScalar sw = src->fZ; src = reinterpret_cast(reinterpret_cast(src) + srcStride); const SkScalar* mat = mx.fMat; typedef SkMatrix M; SkScalar x = sdot(sx, mat[M::kMScaleX], sy, mat[M::kMSkewX], sw, mat[M::kMTransX]); SkScalar y = sdot(sx, mat[M::kMSkewY], sy, mat[M::kMScaleY], sw, mat[M::kMTransY]); SkScalar w = sdot(sx, mat[M::kMPersp0], sy, mat[M::kMPersp1], sw, mat[M::kMPersp2]); dst->set(x, y, w); dst = reinterpret_cast(reinterpret_cast(dst) + dstStride); } while (--count); } } void SkMatrix::mapHomogeneousPoints(SkPoint3 dst[], const SkPoint3 src[], int count) const { SkMatrixPriv::MapHomogeneousPointsWithStride(*this, dst, sizeof(SkPoint3), src, sizeof(SkPoint3), count); } void SkMatrix::mapHomogeneousPoints(SkPoint3 dst[], const SkPoint src[], int count) const { if (this->isIdentity()) { for (int i = 0; i < count; ++i) { dst[i] = { src[i].fX, src[i].fY, 1 }; } } else if (this->hasPerspective()) { for (int i = 0; i < count; ++i) { dst[i] = { fMat[0] * src[i].fX + fMat[1] * src[i].fY + fMat[2], fMat[3] * src[i].fX + fMat[4] * src[i].fY + fMat[5], fMat[6] * src[i].fX + fMat[7] * src[i].fY + fMat[8], }; } } else { // affine for (int i = 0; i < count; ++i) { dst[i] = { fMat[0] * src[i].fX + fMat[1] * src[i].fY + fMat[2], fMat[3] * src[i].fX + fMat[4] * src[i].fY + fMat[5], 1, }; } } } /////////////////////////////////////////////////////////////////////////////// void SkMatrix::mapVectors(SkPoint dst[], const SkPoint src[], int count) const { if (this->hasPerspective()) { SkPoint origin; MapXYProc proc = this->getMapXYProc(); proc(*this, 0, 0, &origin); for (int i = count - 1; i >= 0; --i) { SkPoint tmp; proc(*this, src[i].fX, src[i].fY, &tmp); dst[i].set(tmp.fX - origin.fX, tmp.fY - origin.fY); } } else { SkMatrix tmp = *this; tmp.fMat[kMTransX] = tmp.fMat[kMTransY] = 0; tmp.clearTypeMask(kTranslate_Mask); tmp.mapPoints(dst, src, count); } } static skvx::float4 sort_as_rect(const skvx::float4& ltrb) { skvx::float4 rblt(ltrb[2], ltrb[3], ltrb[0], ltrb[1]); auto min = skvx::min(ltrb, rblt); auto max = skvx::max(ltrb, rblt); // We can extract either pair [0,1] or [2,3] from min and max and be correct, but on // ARM this sequence generates the fastest (a single instruction). return skvx::float4(min[2], min[3], max[0], max[1]); } void SkMatrix::mapRectScaleTranslate(SkRect* dst, const SkRect& src) const { SkASSERT(dst); SkASSERT(this->isScaleTranslate()); SkScalar sx = fMat[kMScaleX]; SkScalar sy = fMat[kMScaleY]; SkScalar tx = fMat[kMTransX]; SkScalar ty = fMat[kMTransY]; skvx::float4 scale(sx, sy, sx, sy); skvx::float4 trans(tx, ty, tx, ty); sort_as_rect(skvx::float4::Load(&src.fLeft) * scale + trans).store(&dst->fLeft); } bool SkMatrix::mapRect(SkRect* dst, const SkRect& src, SkApplyPerspectiveClip pc) const { SkASSERT(dst); if (this->getType() <= kTranslate_Mask) { SkScalar tx = fMat[kMTransX]; SkScalar ty = fMat[kMTransY]; skvx::float4 trans(tx, ty, tx, ty); sort_as_rect(skvx::float4::Load(&src.fLeft) + trans).store(&dst->fLeft); return true; } if (this->isScaleTranslate()) { this->mapRectScaleTranslate(dst, src); return true; } else if (pc == SkApplyPerspectiveClip::kYes && this->hasPerspective()) { SkPath path; path.addRect(src); path.transform(*this); *dst = path.getBounds(); return false; } else { SkPoint quad[4]; src.toQuad(quad); this->mapPoints(quad, quad, 4); dst->setBoundsNoCheck(quad, 4); return this->rectStaysRect(); // might still return true if rotated by 90, etc. } } SkScalar SkMatrix::mapRadius(SkScalar radius) const { SkVector vec[2]; vec[0].set(radius, 0); vec[1].set(0, radius); this->mapVectors(vec, 2); SkScalar d0 = vec[0].length(); SkScalar d1 = vec[1].length(); // return geometric mean return SkScalarSqrt(d0 * d1); } /////////////////////////////////////////////////////////////////////////////// void SkMatrix::Persp_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, SkPoint* pt) { SkASSERT(m.hasPerspective()); SkScalar x = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; SkScalar y = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; SkScalar z = sdot(sx, m.fMat[kMPersp0], sy, m.fMat[kMPersp1]) + m.fMat[kMPersp2]; if (z) { z = 1 / z; } pt->fX = x * z; pt->fY = y * z; } void SkMatrix::RotTrans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, SkPoint* pt) { SkASSERT((m.getType() & (kAffine_Mask | kPerspective_Mask)) == kAffine_Mask); pt->fX = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; pt->fY = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; } void SkMatrix::Rot_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, SkPoint* pt) { SkASSERT((m.getType() & (kAffine_Mask | kPerspective_Mask))== kAffine_Mask); SkASSERT(0 == m.fMat[kMTransX]); SkASSERT(0 == m.fMat[kMTransY]); pt->fX = sdot(sx, m.fMat[kMScaleX], sy, m.fMat[kMSkewX]) + m.fMat[kMTransX]; pt->fY = sdot(sx, m.fMat[kMSkewY], sy, m.fMat[kMScaleY]) + m.fMat[kMTransY]; } void SkMatrix::ScaleTrans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, SkPoint* pt) { SkASSERT((m.getType() & (kScale_Mask | kAffine_Mask | kPerspective_Mask)) == kScale_Mask); pt->fX = sx * m.fMat[kMScaleX] + m.fMat[kMTransX]; pt->fY = sy * m.fMat[kMScaleY] + m.fMat[kMTransY]; } void SkMatrix::Scale_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, SkPoint* pt) { SkASSERT((m.getType() & (kScale_Mask | kAffine_Mask | kPerspective_Mask)) == kScale_Mask); SkASSERT(0 == m.fMat[kMTransX]); SkASSERT(0 == m.fMat[kMTransY]); pt->fX = sx * m.fMat[kMScaleX]; pt->fY = sy * m.fMat[kMScaleY]; } void SkMatrix::Trans_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, SkPoint* pt) { SkASSERT(m.getType() == kTranslate_Mask); pt->fX = sx + m.fMat[kMTransX]; pt->fY = sy + m.fMat[kMTransY]; } void SkMatrix::Identity_xy(const SkMatrix& m, SkScalar sx, SkScalar sy, SkPoint* pt) { SkASSERT(0 == m.getType()); pt->fX = sx; pt->fY = sy; } const SkMatrix::MapXYProc SkMatrix::gMapXYProcs[] = { SkMatrix::Identity_xy, SkMatrix::Trans_xy, SkMatrix::Scale_xy, SkMatrix::ScaleTrans_xy, SkMatrix::Rot_xy, SkMatrix::RotTrans_xy, SkMatrix::Rot_xy, SkMatrix::RotTrans_xy, // repeat the persp proc 8 times SkMatrix::Persp_xy, SkMatrix::Persp_xy, SkMatrix::Persp_xy, SkMatrix::Persp_xy, SkMatrix::Persp_xy, SkMatrix::Persp_xy, SkMatrix::Persp_xy, SkMatrix::Persp_xy }; /////////////////////////////////////////////////////////////////////////////// #if 0 // if its nearly zero (just made up 26, perhaps it should be bigger or smaller) #define PerspNearlyZero(x) SkScalarNearlyZero(x, (1.0f / (1 << 26))) bool SkMatrix::isFixedStepInX() const { return PerspNearlyZero(fMat[kMPersp0]); } SkVector SkMatrix::fixedStepInX(SkScalar y) const { SkASSERT(PerspNearlyZero(fMat[kMPersp0])); if (PerspNearlyZero(fMat[kMPersp1]) && PerspNearlyZero(fMat[kMPersp2] - 1)) { return SkVector::Make(fMat[kMScaleX], fMat[kMSkewY]); } else { SkScalar z = y * fMat[kMPersp1] + fMat[kMPersp2]; return SkVector::Make(fMat[kMScaleX] / z, fMat[kMSkewY] / z); } } #endif /////////////////////////////////////////////////////////////////////////////// static inline bool checkForZero(float x) { return x*x == 0; } bool SkMatrix::Poly2Proc(const SkPoint srcPt[], SkMatrix* dst) { dst->fMat[kMScaleX] = srcPt[1].fY - srcPt[0].fY; dst->fMat[kMSkewY] = srcPt[0].fX - srcPt[1].fX; dst->fMat[kMPersp0] = 0; dst->fMat[kMSkewX] = srcPt[1].fX - srcPt[0].fX; dst->fMat[kMScaleY] = srcPt[1].fY - srcPt[0].fY; dst->fMat[kMPersp1] = 0; dst->fMat[kMTransX] = srcPt[0].fX; dst->fMat[kMTransY] = srcPt[0].fY; dst->fMat[kMPersp2] = 1; dst->setTypeMask(kUnknown_Mask); return true; } bool SkMatrix::Poly3Proc(const SkPoint srcPt[], SkMatrix* dst) { dst->fMat[kMScaleX] = srcPt[2].fX - srcPt[0].fX; dst->fMat[kMSkewY] = srcPt[2].fY - srcPt[0].fY; dst->fMat[kMPersp0] = 0; dst->fMat[kMSkewX] = srcPt[1].fX - srcPt[0].fX; dst->fMat[kMScaleY] = srcPt[1].fY - srcPt[0].fY; dst->fMat[kMPersp1] = 0; dst->fMat[kMTransX] = srcPt[0].fX; dst->fMat[kMTransY] = srcPt[0].fY; dst->fMat[kMPersp2] = 1; dst->setTypeMask(kUnknown_Mask); return true; } bool SkMatrix::Poly4Proc(const SkPoint srcPt[], SkMatrix* dst) { float a1, a2; float x0, y0, x1, y1, x2, y2; x0 = srcPt[2].fX - srcPt[0].fX; y0 = srcPt[2].fY - srcPt[0].fY; x1 = srcPt[2].fX - srcPt[1].fX; y1 = srcPt[2].fY - srcPt[1].fY; x2 = srcPt[2].fX - srcPt[3].fX; y2 = srcPt[2].fY - srcPt[3].fY; /* check if abs(x2) > abs(y2) */ if ( x2 > 0 ? y2 > 0 ? x2 > y2 : x2 > -y2 : y2 > 0 ? -x2 > y2 : x2 < y2) { float denom = sk_ieee_float_divide(x1 * y2, x2) - y1; if (checkForZero(denom)) { return false; } a1 = (((x0 - x1) * y2 / x2) - y0 + y1) / denom; } else { float denom = x1 - sk_ieee_float_divide(y1 * x2, y2); if (checkForZero(denom)) { return false; } a1 = (x0 - x1 - sk_ieee_float_divide((y0 - y1) * x2, y2)) / denom; } /* check if abs(x1) > abs(y1) */ if ( x1 > 0 ? y1 > 0 ? x1 > y1 : x1 > -y1 : y1 > 0 ? -x1 > y1 : x1 < y1) { float denom = y2 - sk_ieee_float_divide(x2 * y1, x1); if (checkForZero(denom)) { return false; } a2 = (y0 - y2 - sk_ieee_float_divide((x0 - x2) * y1, x1)) / denom; } else { float denom = sk_ieee_float_divide(y2 * x1, y1) - x2; if (checkForZero(denom)) { return false; } a2 = (sk_ieee_float_divide((y0 - y2) * x1, y1) - x0 + x2) / denom; } dst->fMat[kMScaleX] = a2 * srcPt[3].fX + srcPt[3].fX - srcPt[0].fX; dst->fMat[kMSkewY] = a2 * srcPt[3].fY + srcPt[3].fY - srcPt[0].fY; dst->fMat[kMPersp0] = a2; dst->fMat[kMSkewX] = a1 * srcPt[1].fX + srcPt[1].fX - srcPt[0].fX; dst->fMat[kMScaleY] = a1 * srcPt[1].fY + srcPt[1].fY - srcPt[0].fY; dst->fMat[kMPersp1] = a1; dst->fMat[kMTransX] = srcPt[0].fX; dst->fMat[kMTransY] = srcPt[0].fY; dst->fMat[kMPersp2] = 1; dst->setTypeMask(kUnknown_Mask); return true; } typedef bool (*PolyMapProc)(const SkPoint[], SkMatrix*); /* Adapted from Rob Johnson's original sample code in QuickDraw GX */ bool SkMatrix::setPolyToPoly(const SkPoint src[], const SkPoint dst[], int count) { if ((unsigned)count > 4) { SkDebugf("--- SkMatrix::setPolyToPoly count out of range %d\n", count); return false; } if (0 == count) { this->reset(); return true; } if (1 == count) { this->setTranslate(dst[0].fX - src[0].fX, dst[0].fY - src[0].fY); return true; } const PolyMapProc gPolyMapProcs[] = { SkMatrix::Poly2Proc, SkMatrix::Poly3Proc, SkMatrix::Poly4Proc }; PolyMapProc proc = gPolyMapProcs[count - 2]; SkMatrix tempMap, result; if (!proc(src, &tempMap)) { return false; } if (!tempMap.invert(&result)) { return false; } if (!proc(dst, &tempMap)) { return false; } this->setConcat(tempMap, result); return true; } /////////////////////////////////////////////////////////////////////////////// enum MinMaxOrBoth { kMin_MinMaxOrBoth, kMax_MinMaxOrBoth, kBoth_MinMaxOrBoth }; template bool get_scale_factor(SkMatrix::TypeMask typeMask, const SkScalar m[9], SkScalar results[/*1 or 2*/]) { if (typeMask & SkMatrix::kPerspective_Mask) { return false; } if (SkMatrix::kIdentity_Mask == typeMask) { results[0] = SK_Scalar1; if (kBoth_MinMaxOrBoth == MIN_MAX_OR_BOTH) { results[1] = SK_Scalar1; } return true; } if (!(typeMask & SkMatrix::kAffine_Mask)) { if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) { results[0] = std::min(SkScalarAbs(m[SkMatrix::kMScaleX]), SkScalarAbs(m[SkMatrix::kMScaleY])); } else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) { results[0] = std::max(SkScalarAbs(m[SkMatrix::kMScaleX]), SkScalarAbs(m[SkMatrix::kMScaleY])); } else { results[0] = SkScalarAbs(m[SkMatrix::kMScaleX]); results[1] = SkScalarAbs(m[SkMatrix::kMScaleY]); if (results[0] > results[1]) { using std::swap; swap(results[0], results[1]); } } return true; } // ignore the translation part of the matrix, just look at 2x2 portion. // compute singular values, take largest or smallest abs value. // [a b; b c] = A^T*A SkScalar a = sdot(m[SkMatrix::kMScaleX], m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewY], m[SkMatrix::kMSkewY]); SkScalar b = sdot(m[SkMatrix::kMScaleX], m[SkMatrix::kMSkewX], m[SkMatrix::kMScaleY], m[SkMatrix::kMSkewY]); SkScalar c = sdot(m[SkMatrix::kMSkewX], m[SkMatrix::kMSkewX], m[SkMatrix::kMScaleY], m[SkMatrix::kMScaleY]); // eigenvalues of A^T*A are the squared singular values of A. // characteristic equation is det((A^T*A) - l*I) = 0 // l^2 - (a + c)l + (ac-b^2) // solve using quadratic equation (divisor is non-zero since l^2 has 1 coeff // and roots are guaranteed to be pos and real). SkScalar bSqd = b * b; // if upper left 2x2 is orthogonal save some math if (bSqd <= SK_ScalarNearlyZero*SK_ScalarNearlyZero) { if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) { results[0] = std::min(a, c); } else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) { results[0] = std::max(a, c); } else { results[0] = a; results[1] = c; if (results[0] > results[1]) { using std::swap; swap(results[0], results[1]); } } } else { SkScalar aminusc = a - c; SkScalar apluscdiv2 = SkScalarHalf(a + c); SkScalar x = SkScalarHalf(SkScalarSqrt(aminusc * aminusc + 4 * bSqd)); if (kMin_MinMaxOrBoth == MIN_MAX_OR_BOTH) { results[0] = apluscdiv2 - x; } else if (kMax_MinMaxOrBoth == MIN_MAX_OR_BOTH) { results[0] = apluscdiv2 + x; } else { results[0] = apluscdiv2 - x; results[1] = apluscdiv2 + x; } } if (!SkScalarIsFinite(results[0])) { return false; } // Due to the floating point inaccuracy, there might be an error in a, b, c // calculated by sdot, further deepened by subsequent arithmetic operations // on them. Therefore, we allow and cap the nearly-zero negative values. if (results[0] < 0) { results[0] = 0; } results[0] = SkScalarSqrt(results[0]); if (kBoth_MinMaxOrBoth == MIN_MAX_OR_BOTH) { if (!SkScalarIsFinite(results[1])) { return false; } if (results[1] < 0) { results[1] = 0; } results[1] = SkScalarSqrt(results[1]); } return true; } SkScalar SkMatrix::getMinScale() const { SkScalar factor; if (get_scale_factor(this->getType(), fMat, &factor)) { return factor; } else { return -1; } } SkScalar SkMatrix::getMaxScale() const { SkScalar factor; if (get_scale_factor(this->getType(), fMat, &factor)) { return factor; } else { return -1; } } bool SkMatrix::getMinMaxScales(SkScalar scaleFactors[2]) const { return get_scale_factor(this->getType(), fMat, scaleFactors); } const SkMatrix& SkMatrix::I() { static constexpr SkMatrix identity; SkASSERT(identity.isIdentity()); return identity; } const SkMatrix& SkMatrix::InvalidMatrix() { static constexpr SkMatrix invalid(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, kTranslate_Mask | kScale_Mask | kAffine_Mask | kPerspective_Mask); return invalid; } bool SkMatrix::decomposeScale(SkSize* scale, SkMatrix* remaining) const { if (this->hasPerspective()) { return false; } const SkScalar sx = SkVector::Length(this->getScaleX(), this->getSkewY()); const SkScalar sy = SkVector::Length(this->getSkewX(), this->getScaleY()); if (!SkScalarIsFinite(sx) || !SkScalarIsFinite(sy) || SkScalarNearlyZero(sx) || SkScalarNearlyZero(sy)) { return false; } if (scale) { scale->set(sx, sy); } if (remaining) { *remaining = *this; remaining->preScale(SkScalarInvert(sx), SkScalarInvert(sy)); } return true; } /////////////////////////////////////////////////////////////////////////////// size_t SkMatrix::writeToMemory(void* buffer) const { // TODO write less for simple matrices static const size_t sizeInMemory = 9 * sizeof(SkScalar); if (buffer) { memcpy(buffer, fMat, sizeInMemory); } return sizeInMemory; } size_t SkMatrix::readFromMemory(const void* buffer, size_t length) { static const size_t sizeInMemory = 9 * sizeof(SkScalar); if (length < sizeInMemory) { return 0; } memcpy(fMat, buffer, sizeInMemory); this->setTypeMask(kUnknown_Mask); // Figure out the type now so that we're thread-safe (void)this->getType(); return sizeInMemory; } void SkMatrix::dump() const { SkString str; str.appendf("[%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f][%8.4f %8.4f %8.4f]", fMat[0], fMat[1], fMat[2], fMat[3], fMat[4], fMat[5], fMat[6], fMat[7], fMat[8]); SkDebugf("%s\n", str.c_str()); } /////////////////////////////////////////////////////////////////////////////// bool SkTreatAsSprite(const SkMatrix& mat, const SkISize& size, const SkSamplingOptions& sampling, bool isAntiAlias) { if (!SkSamplingPriv::NoChangeWithIdentityMatrix(sampling)) { return false; } // Our path aa is 2-bits, and our rect aa is 8, so we could use 8, // but in practice 4 seems enough (still looks smooth) and allows // more slightly fractional cases to fall into the fast (sprite) case. static const unsigned kAntiAliasSubpixelBits = 4; const unsigned subpixelBits = isAntiAlias ? kAntiAliasSubpixelBits : 0; // quick reject on affine or perspective if (mat.getType() & ~(SkMatrix::kScale_Mask | SkMatrix::kTranslate_Mask)) { return false; } // quick success check if (!subpixelBits && !(mat.getType() & ~SkMatrix::kTranslate_Mask)) { return true; } // mapRect supports negative scales, so we eliminate those first if (mat.getScaleX() < 0 || mat.getScaleY() < 0) { return false; } SkRect dst; SkIRect isrc = SkIRect::MakeSize(size); { SkRect src; src.set(isrc); mat.mapRect(&dst, src); } // just apply the translate to isrc isrc.offset(SkScalarRoundToInt(mat.getTranslateX()), SkScalarRoundToInt(mat.getTranslateY())); if (subpixelBits) { isrc.fLeft = SkLeftShift(isrc.fLeft, subpixelBits); isrc.fTop = SkLeftShift(isrc.fTop, subpixelBits); isrc.fRight = SkLeftShift(isrc.fRight, subpixelBits); isrc.fBottom = SkLeftShift(isrc.fBottom, subpixelBits); const float scale = 1 << subpixelBits; dst.fLeft *= scale; dst.fTop *= scale; dst.fRight *= scale; dst.fBottom *= scale; } SkIRect idst; dst.round(&idst); return isrc == idst; } // A square matrix M can be decomposed (via polar decomposition) into two matrices -- // an orthogonal matrix Q and a symmetric matrix S. In turn we can decompose S into U*W*U^T, // where U is another orthogonal matrix and W is a scale matrix. These can be recombined // to give M = (Q*U)*W*U^T, i.e., the product of two orthogonal matrices and a scale matrix. // // The one wrinkle is that traditionally Q may contain a reflection -- the // calculation has been rejiggered to put that reflection into W. bool SkDecomposeUpper2x2(const SkMatrix& matrix, SkPoint* rotation1, SkPoint* scale, SkPoint* rotation2) { SkScalar A = matrix[SkMatrix::kMScaleX]; SkScalar B = matrix[SkMatrix::kMSkewX]; SkScalar C = matrix[SkMatrix::kMSkewY]; SkScalar D = matrix[SkMatrix::kMScaleY]; if (is_degenerate_2x2(A, B, C, D)) { return false; } double w1, w2; SkScalar cos1, sin1; SkScalar cos2, sin2; // do polar decomposition (M = Q*S) SkScalar cosQ, sinQ; double Sa, Sb, Sd; // if M is already symmetric (i.e., M = I*S) if (SkScalarNearlyEqual(B, C)) { cosQ = 1; sinQ = 0; Sa = A; Sb = B; Sd = D; } else { cosQ = A + D; sinQ = C - B; SkScalar reciplen = SkScalarInvert(SkScalarSqrt(cosQ*cosQ + sinQ*sinQ)); cosQ *= reciplen; sinQ *= reciplen; // S = Q^-1*M // we don't calc Sc since it's symmetric Sa = A*cosQ + C*sinQ; Sb = B*cosQ + D*sinQ; Sd = -B*sinQ + D*cosQ; } // Now we need to compute eigenvalues of S (our scale factors) // and eigenvectors (bases for our rotation) // From this, should be able to reconstruct S as U*W*U^T if (SkScalarNearlyZero(SkDoubleToScalar(Sb))) { // already diagonalized cos1 = 1; sin1 = 0; w1 = Sa; w2 = Sd; cos2 = cosQ; sin2 = sinQ; } else { double diff = Sa - Sd; double discriminant = sqrt(diff*diff + 4.0*Sb*Sb); double trace = Sa + Sd; if (diff > 0) { w1 = 0.5*(trace + discriminant); w2 = 0.5*(trace - discriminant); } else { w1 = 0.5*(trace - discriminant); w2 = 0.5*(trace + discriminant); } cos1 = SkDoubleToScalar(Sb); sin1 = SkDoubleToScalar(w1 - Sa); SkScalar reciplen = SkScalarInvert(SkScalarSqrt(cos1*cos1 + sin1*sin1)); cos1 *= reciplen; sin1 *= reciplen; // rotation 2 is composition of Q and U cos2 = cos1*cosQ - sin1*sinQ; sin2 = sin1*cosQ + cos1*sinQ; // rotation 1 is U^T sin1 = -sin1; } if (scale) { scale->fX = SkDoubleToScalar(w1); scale->fY = SkDoubleToScalar(w2); } if (rotation1) { rotation1->fX = cos1; rotation1->fY = sin1; } if (rotation2) { rotation2->fX = cos2; rotation2->fY = sin2; } return true; } /////////////////////////////////////////////////////////////////////////////////////////////////// SkScalar SkMatrixPriv::DifferentialAreaScale(const SkMatrix& m, const SkPoint& p) { // [m00 m01 m02] [f(u,v)] // Assuming M = [m10 m11 m12], define the projected p'(u,v) = [g(u,v)] where // [m20 m12 m22] // [x] [u] // f(u,v) = x(u,v) / w(u,v), g(u,v) = y(u,v) / w(u,v) and [y] = M*[v] // [w] [1] // // Then the differential scale factor between p = (u,v) and p' is |det J|, // where J is the Jacobian for p': [df/du dg/du] // [df/dv dg/dv] // and df/du = (w*dx/du - x*dw/du)/w^2, dg/du = (w*dy/du - y*dw/du)/w^2 // df/dv = (w*dx/dv - x*dw/dv)/w^2, dg/dv = (w*dy/dv - y*dw/dv)/w^2 // // From here, |det J| can be rewritten as |det J'/w^3|, where // [x y w ] [x y w ] // J' = [dx/du dy/du dw/du] = [m00 m10 m20] // [dx/dv dy/dv dw/dv] [m01 m11 m21] SkPoint3 xyw; m.mapHomogeneousPoints(&xyw, &p, 1); if (xyw.fZ < SK_ScalarNearlyZero) { // Reaching the discontinuity of xy/w and where the point would clip to w >= 0 return SK_ScalarInfinity; } SkMatrix jacobian = SkMatrix::MakeAll(xyw.fX, xyw.fY, xyw.fZ, m.getScaleX(), m.getSkewY(), m.getPerspX(), m.getSkewX(), m.getScaleY(), m.getPerspY()); double denom = 1.0 / xyw.fZ; // 1/w denom = denom * denom * denom; // 1/w^3 return SkScalarAbs(SkDoubleToScalar(sk_determinant(jacobian.fMat, true) * denom)); } bool SkMatrixPriv::NearlyAffine(const SkMatrix& m, const SkRect& bounds, SkScalar tolerance) { if (!m.hasPerspective()) { return true; } // The idea here is that we are computing the differential area scale at each corner, // and comparing them with some tolerance value. If they are similar, then we can say // that the transformation is nearly affine. // We can map the four points simultaneously. SkPoint quad[4]; bounds.toQuad(quad); SkPoint3 xyw[4]; m.mapHomogeneousPoints(xyw, quad, 4); // Since the Jacobian is a 3x3 matrix, the determinant is a scalar triple product, // and the initial cross product is constant across all four points. SkPoint3 v1{m.getScaleX(), m.getSkewY(), m.getPerspX()}; SkPoint3 v2{m.getSkewX(), m.getScaleY(), m.getPerspY()}; SkPoint3 detCrossProd = v1.cross(v2); // Start with the calculations at P0. if (xyw[0].fZ < SK_ScalarNearlyZero) { // Reaching the discontinuity of xy/w and where the point would clip to w >= 0 return false; } // Performing a dot product with the pre-w divide transformed point completes // the scalar triple product and the determinant calculation. double det = detCrossProd.dot(xyw[0]); // From that we can compute the differential area scale at P0. double denom = 1.0 / xyw[0].fZ; // 1/w denom = denom * denom * denom; // 1/w^3 SkScalar a0 = SkScalarAbs(SkDoubleToScalar(det*denom)); // Now we compare P0's scale with that at the other three points tolerance *= tolerance; // squared tolerance since we're comparing area for (int i = 1; i < 4; ++i) { if (xyw[i].fZ < SK_ScalarNearlyZero) { // Reaching the discontinuity of xy/w and where the point would clip to w >= 0 return false; } det = detCrossProd.dot(xyw[i]); // completing scalar triple product denom = 1.0 / xyw[i].fZ; // 1/w denom = denom * denom * denom; // 1/w^3 SkScalar a = SkScalarAbs(SkDoubleToScalar(det*denom)); if (!SkScalarNearlyEqual(a0, a, tolerance)) { return false; } } return true; } SkScalar SkMatrixPriv::ComputeResScaleForStroking(const SkMatrix& matrix) { // Not sure how to handle perspective differently, so we just don't try (yet) SkScalar sx = SkPoint::Length(matrix[SkMatrix::kMScaleX], matrix[SkMatrix::kMSkewY]); SkScalar sy = SkPoint::Length(matrix[SkMatrix::kMSkewX], matrix[SkMatrix::kMScaleY]); if (SkScalarsAreFinite(sx, sy)) { SkScalar scale = std::max(sx, sy); if (scale > 0) { return scale; } } return 1; } SkScalar SkInvert2x2Matrix(const SkScalar inMatrix[4], SkScalar outMatrix[4]) { double a00 = inMatrix[0]; double a01 = inMatrix[1]; double a10 = inMatrix[2]; double a11 = inMatrix[3]; // Calculate the determinant double determinant = a00 * a11 - a01 * a10; if (outMatrix) { double invdet = sk_ieee_double_divide(1.0, determinant); outMatrix[0] = a11 * invdet; outMatrix[1] = -a01 * invdet; outMatrix[2] = -a10 * invdet; outMatrix[3] = a00 * invdet; // If 1/det overflows to infinity (i.e. det is denormalized) or any of the inverted matrix // values is non-finite, return zero to indicate a non-invertible matrix. if (!SkScalarsAreFinite(outMatrix, 4)) { determinant = 0.0f; } } return determinant; } SkScalar SkInvert3x3Matrix(const SkScalar inMatrix[9], SkScalar outMatrix[9]) { double a00 = inMatrix[0]; double a01 = inMatrix[1]; double a02 = inMatrix[2]; double a10 = inMatrix[3]; double a11 = inMatrix[4]; double a12 = inMatrix[5]; double a20 = inMatrix[6]; double a21 = inMatrix[7]; double a22 = inMatrix[8]; double b01 = a22 * a11 - a12 * a21; double b11 = -a22 * a10 + a12 * a20; double b21 = a21 * a10 - a11 * a20; // Calculate the determinant double determinant = a00 * b01 + a01 * b11 + a02 * b21; if (outMatrix) { double invdet = sk_ieee_double_divide(1.0, determinant); outMatrix[0] = b01 * invdet; outMatrix[1] = (-a22 * a01 + a02 * a21) * invdet; outMatrix[2] = ( a12 * a01 - a02 * a11) * invdet; outMatrix[3] = b11 * invdet; outMatrix[4] = ( a22 * a00 - a02 * a20) * invdet; outMatrix[5] = (-a12 * a00 + a02 * a10) * invdet; outMatrix[6] = b21 * invdet; outMatrix[7] = (-a21 * a00 + a01 * a20) * invdet; outMatrix[8] = ( a11 * a00 - a01 * a10) * invdet; // If 1/det overflows to infinity (i.e. det is denormalized) or any of the inverted matrix // values is non-finite, return zero to indicate a non-invertible matrix. if (!SkScalarsAreFinite(outMatrix, 9)) { determinant = 0.0f; } } return determinant; } SkScalar SkInvert4x4Matrix(const SkScalar inMatrix[16], SkScalar outMatrix[16]) { double a00 = inMatrix[0]; double a01 = inMatrix[1]; double a02 = inMatrix[2]; double a03 = inMatrix[3]; double a10 = inMatrix[4]; double a11 = inMatrix[5]; double a12 = inMatrix[6]; double a13 = inMatrix[7]; double a20 = inMatrix[8]; double a21 = inMatrix[9]; double a22 = inMatrix[10]; double a23 = inMatrix[11]; double a30 = inMatrix[12]; double a31 = inMatrix[13]; double a32 = inMatrix[14]; double a33 = inMatrix[15]; double b00 = a00 * a11 - a01 * a10; double b01 = a00 * a12 - a02 * a10; double b02 = a00 * a13 - a03 * a10; double b03 = a01 * a12 - a02 * a11; double b04 = a01 * a13 - a03 * a11; double b05 = a02 * a13 - a03 * a12; double b06 = a20 * a31 - a21 * a30; double b07 = a20 * a32 - a22 * a30; double b08 = a20 * a33 - a23 * a30; double b09 = a21 * a32 - a22 * a31; double b10 = a21 * a33 - a23 * a31; double b11 = a22 * a33 - a23 * a32; // Calculate the determinant double determinant = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06; if (outMatrix) { double invdet = sk_ieee_double_divide(1.0, determinant); b00 *= invdet; b01 *= invdet; b02 *= invdet; b03 *= invdet; b04 *= invdet; b05 *= invdet; b06 *= invdet; b07 *= invdet; b08 *= invdet; b09 *= invdet; b10 *= invdet; b11 *= invdet; outMatrix[0] = a11 * b11 - a12 * b10 + a13 * b09; outMatrix[1] = a02 * b10 - a01 * b11 - a03 * b09; outMatrix[2] = a31 * b05 - a32 * b04 + a33 * b03; outMatrix[3] = a22 * b04 - a21 * b05 - a23 * b03; outMatrix[4] = a12 * b08 - a10 * b11 - a13 * b07; outMatrix[5] = a00 * b11 - a02 * b08 + a03 * b07; outMatrix[6] = a32 * b02 - a30 * b05 - a33 * b01; outMatrix[7] = a20 * b05 - a22 * b02 + a23 * b01; outMatrix[8] = a10 * b10 - a11 * b08 + a13 * b06; outMatrix[9] = a01 * b08 - a00 * b10 - a03 * b06; outMatrix[10] = a30 * b04 - a31 * b02 + a33 * b00; outMatrix[11] = a21 * b02 - a20 * b04 - a23 * b00; outMatrix[12] = a11 * b07 - a10 * b09 - a12 * b06; outMatrix[13] = a00 * b09 - a01 * b07 + a02 * b06; outMatrix[14] = a31 * b01 - a30 * b03 - a32 * b00; outMatrix[15] = a20 * b03 - a21 * b01 + a22 * b00; // If 1/det overflows to infinity (i.e. det is denormalized) or any of the inverted matrix // values is non-finite, return zero to indicate a non-invertible matrix. if (!SkScalarsAreFinite(outMatrix, 16)) { determinant = 0.0f; } } return determinant; } struct SkPath_Storage_Equivalent { void* fPtr; int32_t fIndex; uint32_t fFlags; }; static_assert(sizeof(SkPath) == sizeof(SkPath_Storage_Equivalent), "Please keep an eye on SkPath packing."); static float poly_eval(float A, float B, float C, float t) { return (A * t + B) * t + C; } static float poly_eval(float A, float B, float C, float D, float t) { return ((A * t + B) * t + C) * t + D; } //////////////////////////////////////////////////////////////////////////// /** * Path.bounds is defined to be the bounds of all the control points. * If we called bounds.join(r) we would skip r if r was empty, which breaks * our promise. Hence we have a custom joiner that doesn't look at emptiness */ static void joinNoEmptyChecks(SkRect* dst, const SkRect& src) { dst->fLeft = std::min(dst->fLeft, src.fLeft); dst->fTop = std::min(dst->fTop, src.fTop); dst->fRight = std::max(dst->fRight, src.fRight); dst->fBottom = std::max(dst->fBottom, src.fBottom); } static bool is_degenerate(const SkPath& path) { return (path.countVerbs() - SkPathPriv::LeadingMoveToCount(path)) == 0; } class SkAutoDisableDirectionCheck { public: SkAutoDisableDirectionCheck(SkPath* path) : fPath(path) { fSaved = static_cast(fPath->getFirstDirection()); } ~SkAutoDisableDirectionCheck() { fPath->setFirstDirection(fSaved); } private: SkPath* fPath; SkPathFirstDirection fSaved; }; /* This class's constructor/destructor bracket a path editing operation. It is used when we know the bounds of the amount we are going to add to the path (usually a new contour, but not required). It captures some state about the path up front (i.e. if it already has a cached bounds), and then if it can, it updates the cache bounds explicitly, avoiding the need to revisit all of the points in getBounds(). It also notes if the path was originally degenerate, and if so, sets isConvex to true. Thus it can only be used if the contour being added is convex. */ class SkAutoPathBoundsUpdate { public: SkAutoPathBoundsUpdate(SkPath* path, const SkRect& r) : fPath(path), fRect(r) { // Cannot use fRect for our bounds unless we know it is sorted fRect.sort(); // Mark the path's bounds as dirty if (1) they are, or (2) the path // is non-finite, and therefore its bounds are not meaningful fHasValidBounds = path->hasComputedBounds() && path->isFinite(); fEmpty = path->isEmpty(); if (fHasValidBounds && !fEmpty) { joinNoEmptyChecks(&fRect, fPath->getBounds()); } fDegenerate = is_degenerate(*path); } ~SkAutoPathBoundsUpdate() { fPath->setConvexity(fDegenerate ? SkPathConvexity::kConvex : SkPathConvexity::kUnknown); if ((fEmpty || fHasValidBounds) && fRect.isFinite()) { fPath->setBounds(fRect); } } private: SkPath* fPath; SkRect fRect; bool fHasValidBounds; bool fDegenerate; bool fEmpty; }; //////////////////////////////////////////////////////////////////////////// /* Stores the verbs and points as they are given to us, with exceptions: - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic - we insert a Move(0,0) if Line | Quad | Cubic is our first command The iterator does more cleanup, especially if forceClose == true 1. If we encounter degenerate segments, remove them 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt) 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2 4. if we encounter Line | Quad | Cubic after Close, cons up a Move */ //////////////////////////////////////////////////////////////////////////// // flag to require a moveTo if we begin with something else, like lineTo etc. // This will also be the value of lastMoveToIndex for a single contour // ending with close, so countVerbs needs to be checked against 0. #define INITIAL_LASTMOVETOINDEX_VALUE ~0 SkPath::SkPath() : fPathRef(SkPathRef::CreateEmpty()) { this->resetFields(); fIsVolatile = false; } SkPath::SkPath(sk_sp pr, SkPathFillType ft, bool isVolatile, SkPathConvexity ct, SkPathFirstDirection firstDirection) : fPathRef(std::move(pr)) , fLastMoveToIndex(INITIAL_LASTMOVETOINDEX_VALUE) , fConvexity((uint8_t)ct) , fFirstDirection((uint8_t)firstDirection) , fFillType((unsigned)ft) , fIsVolatile(isVolatile) {} void SkPath::resetFields() { //fPathRef is assumed to have been emptied by the caller. fLastMoveToIndex = INITIAL_LASTMOVETOINDEX_VALUE; fFillType = SkToU8(SkPathFillType::kWinding); this->setConvexity(SkPathConvexity::kUnknown); this->setFirstDirection(SkPathFirstDirection::kUnknown); // We don't touch Android's fSourcePath. It's used to track texture garbage collection, so we // don't want to muck with it if it's been set to something non-nullptr. } SkPath::SkPath(const SkPath& that) : fPathRef(SkRef(that.fPathRef.get())) { this->copyFields(that); SkDEBUGCODE(that.validate();) } SkPath::~SkPath() { SkDEBUGCODE(this->validate();) } SkPath& SkPath::operator=(const SkPath& that) { SkDEBUGCODE(that.validate();) if (this != &that) { fPathRef.reset(SkRef(that.fPathRef.get())); this->copyFields(that); } SkDEBUGCODE(this->validate();) return *this; } void SkPath::copyFields(const SkPath& that) { //fPathRef is assumed to have been set by the caller. fLastMoveToIndex = that.fLastMoveToIndex; fFillType = that.fFillType; fIsVolatile = that.fIsVolatile; // Non-atomic assignment of atomic values. this->setConvexity(that.getConvexityOrUnknown()); this->setFirstDirection(that.getFirstDirection()); } bool operator==(const SkPath& a, const SkPath& b) { // note: don't need to look at isConvex or bounds, since just comparing the // raw data is sufficient. return &a == &b || (a.fFillType == b.fFillType && *a.fPathRef == *b.fPathRef); } void SkPath::swap(SkPath& that) { if (this != &that) { fPathRef.swap(that.fPathRef); std::swap(fLastMoveToIndex, that.fLastMoveToIndex); const auto ft = fFillType; fFillType = that.fFillType; that.fFillType = ft; const auto iv = fIsVolatile; fIsVolatile = that.fIsVolatile; that.fIsVolatile = iv; // Non-atomic swaps of atomic values. SkPathConvexity c = this->getConvexityOrUnknown(); this->setConvexity(that.getConvexityOrUnknown()); that.setConvexity(c); SkPathFirstDirection fd = this->getFirstDirection(); this->setFirstDirection(that.getFirstDirection()); that.setFirstDirection(fd); } } bool SkPath::isInterpolatable(const SkPath& compare) const { // need the same structure (verbs, conicweights) and same point-count return fPathRef->fPoints.size() == compare.fPathRef->fPoints.size() && fPathRef->fVerbs == compare.fPathRef->fVerbs && fPathRef->fConicWeights == compare.fPathRef->fConicWeights; } bool SkPath::interpolate(const SkPath& ending, SkScalar weight, SkPath* out) const { int pointCount = fPathRef->countPoints(); if (pointCount != ending.fPathRef->countPoints()) { return false; } if (!pointCount) { return true; } out->reset(); out->addPath(*this); fPathRef->interpolate(*ending.fPathRef, weight, out->fPathRef.get()); return true; } static inline bool check_edge_against_rect(const SkPoint& p0, const SkPoint& p1, const SkRect& rect, SkPathFirstDirection dir) { const SkPoint* edgeBegin; SkVector v; if (SkPathFirstDirection::kCW == dir) { v = p1 - p0; edgeBegin = &p0; } else { v = p0 - p1; edgeBegin = &p1; } if (v.fX || v.fY) { // check the cross product of v with the vec from edgeBegin to each rect corner SkScalar yL = v.fY * (rect.fLeft - edgeBegin->fX); SkScalar xT = v.fX * (rect.fTop - edgeBegin->fY); SkScalar yR = v.fY * (rect.fRight - edgeBegin->fX); SkScalar xB = v.fX * (rect.fBottom - edgeBegin->fY); if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) { return false; } } return true; } bool SkPath::conservativelyContainsRect(const SkRect& rect) const { // This only handles non-degenerate convex paths currently. if (!this->isConvex()) { return false; } SkPathFirstDirection direction = SkPathPriv::ComputeFirstDirection(*this); if (direction == SkPathFirstDirection::kUnknown) { return false; } SkPoint firstPt; SkPoint prevPt; int segmentCount = 0; SkDEBUGCODE(int moveCnt = 0;) for (auto [verb, pts, weight] : SkPathPriv::Iterate(*this)) { if (verb == SkPathVerb::kClose || (segmentCount > 0 && verb == SkPathVerb::kMove)) { // Closing the current contour; but since convexity is a precondition, it's the only // contour that matters. SkASSERT(moveCnt); segmentCount++; break; } else if (verb == SkPathVerb::kMove) { // A move at the start of the contour (or multiple leading moves, in which case we // keep the last one before a non-move verb). SkASSERT(!segmentCount); SkDEBUGCODE(++moveCnt); firstPt = prevPt = pts[0]; } else { int pointCount = SkPathPriv::PtsInVerb((unsigned) verb); SkASSERT(pointCount > 0); if (!SkPathPriv::AllPointsEq(pts, pointCount + 1)) { SkASSERT(moveCnt); int nextPt = pointCount; segmentCount++; if (SkPathVerb::kConic == verb) { SkConic orig; orig.set(pts, *weight); SkPoint quadPts[5]; int count = orig.chopIntoQuadsPOW2(quadPts, 1); SkASSERT_RELEASE(2 == count); if (!check_edge_against_rect(quadPts[0], quadPts[2], rect, direction)) { return false; } if (!check_edge_against_rect(quadPts[2], quadPts[4], rect, direction)) { return false; } } else { if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) { return false; } } prevPt = pts[nextPt]; } } } if (segmentCount) { return check_edge_against_rect(prevPt, firstPt, rect, direction); } return false; } uint32_t SkPath::getGenerationID() const { return fPathRef->genID(fFillType); } SkPath& SkPath::reset() { SkDEBUGCODE(this->validate();) if (fPathRef->unique()) { fPathRef->reset(); } else { fPathRef.reset(SkPathRef::CreateEmpty()); } this->resetFields(); return *this; } SkPath& SkPath::rewind() { SkDEBUGCODE(this->validate();) SkPathRef::Rewind(&fPathRef); this->resetFields(); return *this; } bool SkPath::isLastContourClosed() const { int verbCount = fPathRef->countVerbs(); if (0 == verbCount) { return false; } return kClose_Verb == fPathRef->atVerb(verbCount - 1); } bool SkPath::isLine(SkPoint line[2]) const { int verbCount = fPathRef->countVerbs(); if (2 == verbCount) { SkASSERT(kMove_Verb == fPathRef->atVerb(0)); if (kLine_Verb == fPathRef->atVerb(1)) { SkASSERT(2 == fPathRef->countPoints()); if (line) { const SkPoint* pts = fPathRef->points(); line[0] = pts[0]; line[1] = pts[1]; } return true; } } return false; } bool SkPath::isEmpty() const { SkDEBUGCODE(this->validate();) return 0 == fPathRef->countVerbs(); } bool SkPath::isFinite() const { SkDEBUGCODE(this->validate();) return fPathRef->isFinite(); } bool SkPath::isConvex() const { return SkPathConvexity::kConvex == this->getConvexity(); } const SkRect& SkPath::getBounds() const { return fPathRef->getBounds(); } uint32_t SkPath::getSegmentMasks() const { return fPathRef->getSegmentMasks(); } bool SkPath::isValid() const { return this->isValidImpl() && fPathRef->isValid(); } bool SkPath::hasComputedBounds() const { SkDEBUGCODE(this->validate();) return fPathRef->hasComputedBounds(); } void SkPath::setBounds(const SkRect& rect) { SkPathRef::Editor ed(&fPathRef); ed.setBounds(rect); } SkPathConvexity SkPath::getConvexityOrUnknown() const { return (SkPathConvexity)fConvexity.load(std::memory_order_relaxed); } #ifdef SK_DEBUG void SkPath::validate() const { SkASSERT(this->isValidImpl()); } void SkPath::validateRef() const { // This will SkASSERT if not valid. fPathRef->validate(); } #endif /* Determines if path is a rect by keeping track of changes in direction and looking for a loop either clockwise or counterclockwise. The direction is computed such that: 0: vertical up 1: horizontal left 2: vertical down 3: horizontal right A rectangle cycles up/right/down/left or up/left/down/right. The test fails if: The path is closed, and followed by a line. A second move creates a new endpoint. A diagonal line is parsed. There's more than four changes of direction. There's a discontinuity on the line (e.g., a move in the middle) The line reverses direction. The path contains a quadratic or cubic. The path contains fewer than four points. *The rectangle doesn't complete a cycle. *The final point isn't equal to the first point. *These last two conditions we relax if we have a 3-edge path that would form a rectangle if it were closed (as we do when we fill a path) It's OK if the path has: Several colinear line segments composing a rectangle side. Single points on the rectangle side. The direction takes advantage of the corners found since opposite sides must travel in opposite directions. FIXME: Allow colinear quads and cubics to be treated like lines. FIXME: If the API passes fill-only, return true if the filled stroke is a rectangle, though the caller failed to close the path. directions values: 0x1 is set if the segment is horizontal 0x2 is set if the segment is moving to the right or down thus: two directions are opposites iff (dirA ^ dirB) == 0x2 two directions are perpendicular iff (dirA ^ dirB) == 0x1 */ static int rect_make_dir(SkScalar dx, SkScalar dy) { return ((0 != dx) << 0) | ((dx > 0 || dy > 0) << 1); } bool SkPath::isRect(SkRect* rect, bool* isClosed, SkPathDirection* direction) const { SkDEBUGCODE(this->validate();) int currVerb = 0; const SkPoint* pts = fPathRef->points(); return SkPathPriv::IsRectContour(*this, false, &currVerb, &pts, isClosed, direction, rect); } bool SkPath::isOval(SkRect* bounds) const { return SkPathPriv::IsOval(*this, bounds, nullptr, nullptr); } bool SkPath::isRRect(SkRRect* rrect) const { return SkPathPriv::IsRRect(*this, rrect, nullptr, nullptr); } int SkPath::countPoints() const { return fPathRef->countPoints(); } int SkPath::getPoints(SkPoint dst[], int max) const { SkDEBUGCODE(this->validate();) SkASSERT(max >= 0); SkASSERT(!max || dst); int count = std::min(max, fPathRef->countPoints()); sk_careful_memcpy(dst, fPathRef->points(), count * sizeof(SkPoint)); return fPathRef->countPoints(); } SkPoint SkPath::getPoint(int index) const { if ((unsigned)index < (unsigned)fPathRef->countPoints()) { return fPathRef->atPoint(index); } return SkPoint::Make(0, 0); } int SkPath::countVerbs() const { return fPathRef->countVerbs(); } int SkPath::getVerbs(uint8_t dst[], int max) const { SkDEBUGCODE(this->validate();) SkASSERT(max >= 0); SkASSERT(!max || dst); int count = std::min(max, fPathRef->countVerbs()); if (count) { memcpy(dst, fPathRef->verbsBegin(), count); } return fPathRef->countVerbs(); } size_t SkPath::approximateBytesUsed() const { size_t size = sizeof (SkPath); if (fPathRef != nullptr) { size += fPathRef->approximateBytesUsed(); } return size; } bool SkPath::getLastPt(SkPoint* lastPt) const { SkDEBUGCODE(this->validate();) int count = fPathRef->countPoints(); if (count > 0) { if (lastPt) { *lastPt = fPathRef->atPoint(count - 1); } return true; } if (lastPt) { lastPt->set(0, 0); } return false; } void SkPath::setPt(int index, SkScalar x, SkScalar y) { SkDEBUGCODE(this->validate();) int count = fPathRef->countPoints(); if (count <= index) { return; } else { SkPathRef::Editor ed(&fPathRef); ed.atPoint(index)->set(x, y); } } void SkPath::setLastPt(SkScalar x, SkScalar y) { SkDEBUGCODE(this->validate();) int count = fPathRef->countPoints(); if (count == 0) { this->moveTo(x, y); } else { SkPathRef::Editor ed(&fPathRef); ed.atPoint(count-1)->set(x, y); } } // This is the public-facing non-const setConvexity(). void SkPath::setConvexity(SkPathConvexity c) { fConvexity.store((uint8_t)c, std::memory_order_relaxed); } // Const hooks for working with fConvexity and fFirstDirection from const methods. void SkPath::setConvexity(SkPathConvexity c) const { fConvexity.store((uint8_t)c, std::memory_order_relaxed); } void SkPath::setFirstDirection(SkPathFirstDirection d) const { fFirstDirection.store((uint8_t)d, std::memory_order_relaxed); } SkPathFirstDirection SkPath::getFirstDirection() const { return (SkPathFirstDirection)fFirstDirection.load(std::memory_order_relaxed); } bool SkPath::isConvexityAccurate() const { SkPathConvexity convexity = this->getConvexityOrUnknown(); if (convexity != SkPathConvexity::kUnknown) { auto conv = this->computeConvexity(); if (conv != convexity) { SkASSERT(false); return false; } } return true; } SkPathConvexity SkPath::getConvexity() const { // Enable once we fix all the bugs // SkDEBUGCODE(this->isConvexityAccurate()); SkPathConvexity convexity = this->getConvexityOrUnknown(); if (convexity == SkPathConvexity::kUnknown) { convexity = this->computeConvexity(); } SkASSERT(convexity != SkPathConvexity::kUnknown); return convexity; } ////////////////////////////////////////////////////////////////////////////// // Construction methods SkPath& SkPath::dirtyAfterEdit() { this->setConvexity(SkPathConvexity::kUnknown); this->setFirstDirection(SkPathFirstDirection::kUnknown); #ifdef SK_DEBUG // enable this as needed for testing, but it slows down some chrome tests so much // that they don't complete, so we don't enable it by default // e.g. TEST(IdentifiabilityPaintOpDigestTest, MassiveOpSkipped) if (this->countVerbs() < 16) { SkASSERT(fPathRef->dataMatchesVerbs()); } #endif return *this; } void SkPath::incReserve(int inc) { SkDEBUGCODE(this->validate();) if (inc > 0) { SkPathRef::Editor(&fPathRef, inc, inc); } SkDEBUGCODE(this->validate();) } SkPath& SkPath::moveTo(SkScalar x, SkScalar y) { SkDEBUGCODE(this->validate();) SkPathRef::Editor ed(&fPathRef); // remember our index fLastMoveToIndex = fPathRef->countPoints(); ed.growForVerb(kMove_Verb)->set(x, y); return this->dirtyAfterEdit(); } SkPath& SkPath::rMoveTo(SkScalar x, SkScalar y) { SkPoint pt = {0,0}; int count = fPathRef->countPoints(); if (count > 0) { if (fLastMoveToIndex >= 0) { pt = fPathRef->atPoint(count - 1); } else { pt = fPathRef->atPoint(~fLastMoveToIndex); } } return this->moveTo(pt.fX + x, pt.fY + y); } void SkPath::injectMoveToIfNeeded() { if (fLastMoveToIndex < 0) { SkScalar x, y; if (fPathRef->countVerbs() == 0) { x = y = 0; } else { const SkPoint& pt = fPathRef->atPoint(~fLastMoveToIndex); x = pt.fX; y = pt.fY; } this->moveTo(x, y); } } SkPath& SkPath::lineTo(SkScalar x, SkScalar y) { SkDEBUGCODE(this->validate();) this->injectMoveToIfNeeded(); SkPathRef::Editor ed(&fPathRef); ed.growForVerb(kLine_Verb)->set(x, y); return this->dirtyAfterEdit(); } SkPath& SkPath::rLineTo(SkScalar x, SkScalar y) { this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). SkPoint pt; this->getLastPt(&pt); return this->lineTo(pt.fX + x, pt.fY + y); } SkPath& SkPath::quadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { SkDEBUGCODE(this->validate();) this->injectMoveToIfNeeded(); SkPathRef::Editor ed(&fPathRef); SkPoint* pts = ed.growForVerb(kQuad_Verb); pts[0].set(x1, y1); pts[1].set(x2, y2); return this->dirtyAfterEdit(); } SkPath& SkPath::rQuadTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2) { this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). SkPoint pt; this->getLastPt(&pt); return this->quadTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2); } SkPath& SkPath::conicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar w) { // check for <= 0 or NaN with this test if (!(w > 0)) { this->lineTo(x2, y2); } else if (!SkScalarIsFinite(w)) { this->lineTo(x1, y1); this->lineTo(x2, y2); } else if (SK_Scalar1 == w) { this->quadTo(x1, y1, x2, y2); } else { SkDEBUGCODE(this->validate();) this->injectMoveToIfNeeded(); SkPathRef::Editor ed(&fPathRef); SkPoint* pts = ed.growForVerb(kConic_Verb, w); pts[0].set(x1, y1); pts[1].set(x2, y2); (void)this->dirtyAfterEdit(); } return *this; } SkPath& SkPath::rConicTo(SkScalar dx1, SkScalar dy1, SkScalar dx2, SkScalar dy2, SkScalar w) { this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). SkPoint pt; this->getLastPt(&pt); return this->conicTo(pt.fX + dx1, pt.fY + dy1, pt.fX + dx2, pt.fY + dy2, w); } SkPath& SkPath::cubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3) { SkDEBUGCODE(this->validate();) this->injectMoveToIfNeeded(); SkPathRef::Editor ed(&fPathRef); SkPoint* pts = ed.growForVerb(kCubic_Verb); pts[0].set(x1, y1); pts[1].set(x2, y2); pts[2].set(x3, y3); return this->dirtyAfterEdit(); } SkPath& SkPath::rCubicTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar x3, SkScalar y3) { this->injectMoveToIfNeeded(); // This can change the result of this->getLastPt(). SkPoint pt; this->getLastPt(&pt); return this->cubicTo(pt.fX + x1, pt.fY + y1, pt.fX + x2, pt.fY + y2, pt.fX + x3, pt.fY + y3); } SkPath& SkPath::close() { SkDEBUGCODE(this->validate();) int count = fPathRef->countVerbs(); if (count > 0) { switch (fPathRef->atVerb(count - 1)) { case kLine_Verb: case kQuad_Verb: case kConic_Verb: case kCubic_Verb: case kMove_Verb: { SkPathRef::Editor ed(&fPathRef); ed.growForVerb(kClose_Verb); break; } case kClose_Verb: // don't add a close if it's the first verb or a repeat break; default: SkDEBUGFAIL("unexpected verb"); break; } } // signal that we need a moveTo to follow us (unless we're done) #if 0 if (fLastMoveToIndex >= 0) { fLastMoveToIndex = ~fLastMoveToIndex; } #else fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1); #endif return *this; } /////////////////////////////////////////////////////////////////////////////// static void assert_known_direction(SkPathDirection dir) { SkASSERT(SkPathDirection::kCW == dir || SkPathDirection::kCCW == dir); } SkPath& SkPath::addRect(const SkRect &rect, SkPathDirection dir, unsigned startIndex) { assert_known_direction(dir); this->setFirstDirection(this->hasOnlyMoveTos() ? (SkPathFirstDirection)dir : SkPathFirstDirection::kUnknown); SkAutoDisableDirectionCheck addc(this); SkAutoPathBoundsUpdate apbu(this, rect); SkDEBUGCODE(int initialVerbCount = this->countVerbs()); const int kVerbs = 5; // moveTo + 3x lineTo + close this->incReserve(kVerbs); SkPath_RectPointIterator iter(rect, dir, startIndex); this->moveTo(iter.current()); this->lineTo(iter.next()); this->lineTo(iter.next()); this->lineTo(iter.next()); this->close(); SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); return *this; } SkPath& SkPath::addPoly(const SkPoint pts[], int count, bool close) { SkDEBUGCODE(this->validate();) if (count <= 0) { return *this; } fLastMoveToIndex = fPathRef->countPoints(); // +close makes room for the extra kClose_Verb SkPathRef::Editor ed(&fPathRef, count+close, count); ed.growForVerb(kMove_Verb)->set(pts[0].fX, pts[0].fY); if (count > 1) { SkPoint* p = ed.growForRepeatedVerb(kLine_Verb, count - 1); memcpy(p, &pts[1], (count-1) * sizeof(SkPoint)); } if (close) { ed.growForVerb(kClose_Verb); fLastMoveToIndex ^= ~fLastMoveToIndex >> (8 * sizeof(fLastMoveToIndex) - 1); } (void)this->dirtyAfterEdit(); SkDEBUGCODE(this->validate();) return *this; } static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, SkPoint* pt) { if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) { // Chrome uses this path to move into and out of ovals. If not // treated as a special case the moves can distort the oval's // bounding box (and break the circle special case). pt->set(oval.fRight, oval.centerY()); return true; } else if (0 == oval.width() && 0 == oval.height()) { // Chrome will sometimes create 0 radius round rects. Having degenerate // quad segments in the path prevents the path from being recognized as // a rect. // TODO: optimizing the case where only one of width or height is zero // should also be considered. This case, however, doesn't seem to be // as common as the single point case. pt->set(oval.fRight, oval.fTop); return true; } return false; } // Return the unit vectors pointing at the start/stop points for the given start/sweep angles // static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle, SkVector* startV, SkVector* stopV, SkRotationDirection* dir) { SkScalar startRad = SkDegreesToRadians(startAngle), stopRad = SkDegreesToRadians(startAngle + sweepAngle); startV->fY = SkScalarSinSnapToZero(startRad); startV->fX = SkScalarCosSnapToZero(startRad); stopV->fY = SkScalarSinSnapToZero(stopRad); stopV->fX = SkScalarCosSnapToZero(stopRad); /* If the sweep angle is nearly (but less than) 360, then due to precision loss in radians-conversion and/or sin/cos, we may end up with coincident vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead of drawing a nearly complete circle (good). e.g. canvas.drawArc(0, 359.99, ...) -vs- canvas.drawArc(0, 359.9, ...) We try to detect this edge case, and tweak the stop vector */ if (*startV == *stopV) { SkScalar sw = SkScalarAbs(sweepAngle); if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) { // make a guess at a tiny angle (in radians) to tweak by SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle); // not sure how much will be enough, so we use a loop do { stopRad -= deltaRad; stopV->fY = SkScalarSinSnapToZero(stopRad); stopV->fX = SkScalarCosSnapToZero(stopRad); } while (*startV == *stopV); } } *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection; } /** * If this returns 0, then the caller should just line-to the singlePt, else it should * ignore singlePt and append the specified number of conics. */ static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop, SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc], SkPoint* singlePt) { SkMatrix matrix; matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height())); matrix.postTranslate(oval.centerX(), oval.centerY()); int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics); if (0 == count) { matrix.mapXY(stop.x(), stop.y(), singlePt); } return count; } SkPath& SkPath::addRoundRect(const SkRect& rect, const SkScalar radii[], SkPathDirection dir) { SkRRect rrect; rrect.setRectRadii(rect, (const SkVector*) radii); return this->addRRect(rrect, dir); } SkPath& SkPath::addRRect(const SkRRect& rrect, SkPathDirection dir) { // legacy start indices: 6 (CW) and 7(CCW) return this->addRRect(rrect, dir, dir == SkPathDirection::kCW ? 6 : 7); } SkPath& SkPath::addRRect(const SkRRect &rrect, SkPathDirection dir, unsigned startIndex) { assert_known_direction(dir); bool isRRect = hasOnlyMoveTos(); const SkRect& bounds = rrect.getBounds(); if (rrect.isRect() || rrect.isEmpty()) { // degenerate(rect) => radii points are collapsing this->addRect(bounds, dir, (startIndex + 1) / 2); } else if (rrect.isOval()) { // degenerate(oval) => line points are collapsing this->addOval(bounds, dir, startIndex / 2); } else { this->setFirstDirection(this->hasOnlyMoveTos() ? (SkPathFirstDirection)dir : SkPathFirstDirection::kUnknown); SkAutoPathBoundsUpdate apbu(this, bounds); SkAutoDisableDirectionCheck addc(this); // we start with a conic on odd indices when moving CW vs. even indices when moving CCW const bool startsWithConic = ((startIndex & 1) == (dir == SkPathDirection::kCW)); const SkScalar weight = SK_ScalarRoot2Over2; SkDEBUGCODE(int initialVerbCount = this->countVerbs()); const int kVerbs = startsWithConic ? 9 // moveTo + 4x conicTo + 3x lineTo + close : 10; // moveTo + 4x lineTo + 4x conicTo + close this->incReserve(kVerbs); SkPath_RRectPointIterator rrectIter(rrect, dir, startIndex); // Corner iterator indices follow the collapsed radii model, // adjusted such that the start pt is "behind" the radii start pt. const unsigned rectStartIndex = startIndex / 2 + (dir == SkPathDirection::kCW ? 0 : 1); SkPath_RectPointIterator rectIter(bounds, dir, rectStartIndex); this->moveTo(rrectIter.current()); if (startsWithConic) { for (unsigned i = 0; i < 3; ++i) { this->conicTo(rectIter.next(), rrectIter.next(), weight); this->lineTo(rrectIter.next()); } this->conicTo(rectIter.next(), rrectIter.next(), weight); // final lineTo handled by close(). } else { for (unsigned i = 0; i < 4; ++i) { this->lineTo(rrectIter.next()); this->conicTo(rectIter.next(), rrectIter.next(), weight); } } this->close(); SkPathRef::Editor ed(&fPathRef); ed.setIsRRect(isRRect, dir == SkPathDirection::kCCW, startIndex % 8); SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); } SkDEBUGCODE(fPathRef->validate();) return *this; } bool SkPath::hasOnlyMoveTos() const { int count = fPathRef->countVerbs(); const uint8_t* verbs = fPathRef->verbsBegin(); for (int i = 0; i < count; ++i) { if (*verbs == kLine_Verb || *verbs == kQuad_Verb || *verbs == kConic_Verb || *verbs == kCubic_Verb) { return false; } ++verbs; } return true; } bool SkPath::isZeroLengthSincePoint(int startPtIndex) const { int count = fPathRef->countPoints() - startPtIndex; if (count < 2) { return true; } const SkPoint* pts = fPathRef->points() + startPtIndex; const SkPoint& first = *pts; for (int index = 1; index < count; ++index) { if (first != pts[index]) { return false; } } return true; } SkPath& SkPath::addRoundRect(const SkRect& rect, SkScalar rx, SkScalar ry, SkPathDirection dir) { assert_known_direction(dir); if (rx < 0 || ry < 0) { return *this; } SkRRect rrect; rrect.setRectXY(rect, rx, ry); return this->addRRect(rrect, dir); } SkPath& SkPath::addOval(const SkRect& oval, SkPathDirection dir) { // legacy start index: 1 return this->addOval(oval, dir, 1); } SkPath& SkPath::addOval(const SkRect &oval, SkPathDirection dir, unsigned startPointIndex) { assert_known_direction(dir); /* If addOval() is called after previous moveTo(), this path is still marked as an oval. This is used to fit into WebKit's calling sequences. We can't simply check isEmpty() in this case, as additional moveTo() would mark the path non empty. */ bool isOval = hasOnlyMoveTos(); if (isOval) { this->setFirstDirection((SkPathFirstDirection)dir); } else { this->setFirstDirection(SkPathFirstDirection::kUnknown); } SkAutoDisableDirectionCheck addc(this); SkAutoPathBoundsUpdate apbu(this, oval); SkDEBUGCODE(int initialVerbCount = this->countVerbs()); const int kVerbs = 6; // moveTo + 4x conicTo + close this->incReserve(kVerbs); SkPath_OvalPointIterator ovalIter(oval, dir, startPointIndex); // The corner iterator pts are tracking "behind" the oval/radii pts. SkPath_RectPointIterator rectIter(oval, dir, startPointIndex + (dir == SkPathDirection::kCW ? 0 : 1)); const SkScalar weight = SK_ScalarRoot2Over2; this->moveTo(ovalIter.current()); for (unsigned i = 0; i < 4; ++i) { this->conicTo(rectIter.next(), ovalIter.next(), weight); } this->close(); SkASSERT(this->countVerbs() == initialVerbCount + kVerbs); SkPathRef::Editor ed(&fPathRef); ed.setIsOval(isOval, SkPathDirection::kCCW == dir, startPointIndex % 4); return *this; } SkPath& SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, SkPathDirection dir) { if (r > 0) { this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir); } return *this; } SkPath& SkPath::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) { if (oval.width() < 0 || oval.height() < 0) { return *this; } startAngle = SkScalarMod(startAngle, 360.0f); if (fPathRef->countVerbs() == 0) { forceMoveTo = true; } SkPoint lonePt; if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) { return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt); } SkVector startV, stopV; SkRotationDirection dir; angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir); SkPoint singlePt; // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous // arcs from the same oval. auto addPt = [&forceMoveTo, this](const SkPoint& pt) { SkPoint lastPt; if (forceMoveTo) { this->moveTo(pt); } else if (!this->getLastPt(&lastPt) || !SkScalarNearlyEqual(lastPt.fX, pt.fX) || !SkScalarNearlyEqual(lastPt.fY, pt.fY)) { this->lineTo(pt); } }; // At this point, we know that the arc is not a lone point, but startV == stopV // indicates that the sweepAngle is too small such that angles_to_unit_vectors // cannot handle it. if (startV == stopV) { SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle); SkScalar radiusX = oval.width() / 2; SkScalar radiusY = oval.height() / 2; // We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle // is very small and radius is huge, the expected behavior here is to draw a line. But // calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot. singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle), oval.centerY() + radiusY * SkScalarSin(endAngle)); addPt(singlePt); return *this; } SkConic conics[SkConic::kMaxConicsForArc]; int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt); if (count) { this->incReserve(count * 2 + 1); const SkPoint& pt = conics[0].fPts[0]; addPt(pt); for (int i = 0; i < count; ++i) { this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW); } } else { addPt(singlePt); } return *this; } // This converts the SVG arc to conics. // Partly adapted from Niko's code in kdelibs/kdecore/svgicons. // Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic() // See also SVG implementation notes: // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter // Note that arcSweep bool value is flipped from the original implementation. SkPath& SkPath::arcTo(SkScalar rx, SkScalar ry, SkScalar angle, SkPath::ArcSize arcLarge, SkPathDirection arcSweep, SkScalar x, SkScalar y) { this->injectMoveToIfNeeded(); SkPoint srcPts[2]; this->getLastPt(&srcPts[0]); // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") // joining the endpoints. // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters if (!rx || !ry) { return this->lineTo(x, y); } // If the current point and target point for the arc are identical, it should be treated as a // zero length path. This ensures continuity in animations. srcPts[1].set(x, y); if (srcPts[0] == srcPts[1]) { return this->lineTo(x, y); } rx = SkScalarAbs(rx); ry = SkScalarAbs(ry); SkVector midPointDistance = srcPts[0] - srcPts[1]; midPointDistance *= 0.5f; SkMatrix pointTransform; pointTransform.setRotate(-angle); SkPoint transformedMidPoint; pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1); SkScalar squareRx = rx * rx; SkScalar squareRy = ry * ry; SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX; SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY; // Check if the radii are big enough to draw the arc, scale radii if not. // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii SkScalar radiiScale = squareX / squareRx + squareY / squareRy; if (radiiScale > 1) { radiiScale = SkScalarSqrt(radiiScale); rx *= radiiScale; ry *= radiiScale; } pointTransform.setScale(1 / rx, 1 / ry); pointTransform.preRotate(-angle); SkPoint unitPts[2]; pointTransform.mapPoints(unitPts, srcPts, (int) std::size(unitPts)); SkVector delta = unitPts[1] - unitPts[0]; SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY; SkScalar scaleFactorSquared = std::max(1 / d - 0.25f, 0.f); SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared); if ((arcSweep == SkPathDirection::kCCW) != SkToBool(arcLarge)) { // flipped from the original implementation scaleFactor = -scaleFactor; } delta.scale(scaleFactor); SkPoint centerPoint = unitPts[0] + unitPts[1]; centerPoint *= 0.5f; centerPoint.offset(-delta.fY, delta.fX); unitPts[0] -= centerPoint; unitPts[1] -= centerPoint; SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX); SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX); SkScalar thetaArc = theta2 - theta1; if (thetaArc < 0 && (arcSweep == SkPathDirection::kCW)) { // arcSweep flipped from the original implementation thetaArc += SK_ScalarPI * 2; } else if (thetaArc > 0 && (arcSweep != SkPathDirection::kCW)) { // arcSweep flipped from the original implementation thetaArc -= SK_ScalarPI * 2; } // Very tiny angles cause our subsequent math to go wonky (skbug.com/9272) // so we do a quick check here. The precise tolerance amount is just made up. // PI/million happens to fix the bug in 9272, but a larger value is probably // ok too. if (SkScalarAbs(thetaArc) < (SK_ScalarPI / (1000 * 1000))) { return this->lineTo(x, y); } pointTransform.setRotate(angle); pointTransform.preScale(rx, ry); // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3))); SkScalar thetaWidth = thetaArc / segments; SkScalar t = SkScalarTan(0.5f * thetaWidth); if (!SkScalarIsFinite(t)) { return *this; } SkScalar startTheta = theta1; SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf); auto scalar_is_integer = [](SkScalar scalar) -> bool { return scalar == SkScalarFloorToScalar(scalar); }; bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) && scalar_is_integer(rx) && scalar_is_integer(ry) && scalar_is_integer(x) && scalar_is_integer(y); for (int i = 0; i < segments; ++i) { SkScalar endTheta = startTheta + thetaWidth, sinEndTheta = SkScalarSinSnapToZero(endTheta), cosEndTheta = SkScalarCosSnapToZero(endTheta); unitPts[1].set(cosEndTheta, sinEndTheta); unitPts[1] += centerPoint; unitPts[0] = unitPts[1]; unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta); SkPoint mapped[2]; pointTransform.mapPoints(mapped, unitPts, (int) std::size(unitPts)); /* Computing the arc width introduces rounding errors that cause arcs to start outside their marks. A round rect may lose convexity as a result. If the input values are on integers, place the conic on integers as well. */ if (expectIntegers) { for (SkPoint& point : mapped) { point.fX = SkScalarRoundToScalar(point.fX); point.fY = SkScalarRoundToScalar(point.fY); } } this->conicTo(mapped[0], mapped[1], w); startTheta = endTheta; } // The final point should match the input point (by definition); replace it to // ensure that rounding errors in the above math don't cause any problems. this->setLastPt(x, y); return *this; } SkPath& SkPath::rArcTo(SkScalar rx, SkScalar ry, SkScalar xAxisRotate, SkPath::ArcSize largeArc, SkPathDirection sweep, SkScalar dx, SkScalar dy) { SkPoint currentPoint; this->getLastPt(¤tPoint); return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep, currentPoint.fX + dx, currentPoint.fY + dy); } SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) { if (oval.isEmpty() || 0 == sweepAngle) { return *this; } const SkScalar kFullCircleAngle = SkIntToScalar(360); if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) { // We can treat the arc as an oval if it begins at one of our legal starting positions. // See SkPath::addOval() docs. SkScalar startOver90 = startAngle / 90.f; SkScalar startOver90I = SkScalarRoundToScalar(startOver90); SkScalar error = startOver90 - startOver90I; if (SkScalarNearlyEqual(error, 0)) { // Index 1 is at startAngle == 0. SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f); startIndex = startIndex < 0 ? startIndex + 4.f : startIndex; return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW, (unsigned) startIndex); } } return this->arcTo(oval, startAngle, sweepAngle, true); } /* Need to handle the case when the angle is sharp, and our computed end-points for the arc go behind pt1 and/or p2... */ SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) { this->injectMoveToIfNeeded(); if (radius == 0) { return this->lineTo(x1, y1); } // need to know our prev pt so we can construct tangent vectors SkPoint start; this->getLastPt(&start); // need double precision for these calcs. skvx::double2 befored = normalize(skvx::double2{x1 - start.fX, y1 - start.fY}); skvx::double2 afterd = normalize(skvx::double2{x2 - x1, y2 - y1}); double cosh = dot(befored, afterd); double sinh = cross(befored, afterd); // If the previous point equals the first point, befored will be denormalized. // If the two points equal, afterd will be denormalized. // If the second point equals the first point, sinh will be zero. // In all these cases, we cannot construct an arc, so we construct a line to the first point. if (!isfinite(befored) || !isfinite(afterd) || SkScalarNearlyZero(SkDoubleToScalar(sinh))) { return this->lineTo(x1, y1); } // safe to convert back to floats now SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh)); SkScalar xx = x1 - dist * befored[0]; SkScalar yy = y1 - dist * befored[1]; SkVector after = SkVector::Make(afterd[0], afterd[1]); after.setLength(dist); this->lineTo(xx, yy); SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5)); return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight); } /////////////////////////////////////////////////////////////////////////////// SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) { SkMatrix matrix; matrix.setTranslate(dx, dy); return this->addPath(path, matrix, mode); } SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) { if (srcPath.isEmpty()) { return *this; } // Detect if we're trying to add ourself const SkPath* src = &srcPath; SkTLazy tmp; if (this == src) { src = tmp.set(srcPath); } if (kAppend_AddPathMode == mode && !matrix.hasPerspective()) { if (src->fLastMoveToIndex >= 0) { fLastMoveToIndex = src->fLastMoveToIndex + this->countPoints(); } else { fLastMoveToIndex = src->fLastMoveToIndex - this->countPoints(); } SkPathRef::Editor ed(&fPathRef); auto [newPts, newWeights] = ed.growForVerbsInPath(*src->fPathRef); matrix.mapPoints(newPts, src->fPathRef->points(), src->countPoints()); if (int numWeights = src->fPathRef->countWeights()) { memcpy(newWeights, src->fPathRef->conicWeights(), numWeights * sizeof(newWeights[0])); } return this->dirtyAfterEdit(); } SkMatrixPriv::MapPtsProc mapPtsProc = SkMatrixPriv::GetMapPtsProc(matrix); bool firstVerb = true; for (auto [verb, pts, w] : SkPathPriv::Iterate(*src)) { SkPoint mappedPts[3]; switch (verb) { case SkPathVerb::kMove: mapPtsProc(matrix, mappedPts, &pts[0], 1); if (firstVerb && mode == kExtend_AddPathMode && !isEmpty()) { injectMoveToIfNeeded(); // In case last contour is closed SkPoint lastPt; // don't add lineTo if it is degenerate if (!this->getLastPt(&lastPt) || lastPt != mappedPts[0]) { this->lineTo(mappedPts[0]); } } else { this->moveTo(mappedPts[0]); } break; case SkPathVerb::kLine: mapPtsProc(matrix, mappedPts, &pts[1], 1); this->lineTo(mappedPts[0]); break; case SkPathVerb::kQuad: mapPtsProc(matrix, mappedPts, &pts[1], 2); this->quadTo(mappedPts[0], mappedPts[1]); break; case SkPathVerb::kConic: mapPtsProc(matrix, mappedPts, &pts[1], 2); this->conicTo(mappedPts[0], mappedPts[1], *w); break; case SkPathVerb::kCubic: mapPtsProc(matrix, mappedPts, &pts[1], 3); this->cubicTo(mappedPts[0], mappedPts[1], mappedPts[2]); break; case SkPathVerb::kClose: this->close(); break; } firstVerb = false; } return *this; } /////////////////////////////////////////////////////////////////////////////// // ignore the last point of the 1st contour SkPath& SkPath::reversePathTo(const SkPath& path) { if (path.fPathRef->fVerbs.empty()) { return *this; } const uint8_t* verbs = path.fPathRef->verbsEnd(); const uint8_t* verbsBegin = path.fPathRef->verbsBegin(); SkASSERT(verbsBegin[0] == kMove_Verb); const SkPoint* pts = path.fPathRef->pointsEnd() - 1; const SkScalar* conicWeights = path.fPathRef->conicWeightsEnd(); while (verbs > verbsBegin) { uint8_t v = *--verbs; pts -= SkPathPriv::PtsInVerb(v); switch (v) { case kMove_Verb: // if the path has multiple contours, stop after reversing the last return *this; case kLine_Verb: this->lineTo(pts[0]); break; case kQuad_Verb: this->quadTo(pts[1], pts[0]); break; case kConic_Verb: this->conicTo(pts[1], pts[0], *--conicWeights); break; case kCubic_Verb: this->cubicTo(pts[2], pts[1], pts[0]); break; case kClose_Verb: break; default: SkDEBUGFAIL("bad verb"); break; } } return *this; } SkPath& SkPath::reverseAddPath(const SkPath& srcPath) { // Detect if we're trying to add ourself const SkPath* src = &srcPath; SkTLazy tmp; if (this == src) { src = tmp.set(srcPath); } const uint8_t* verbsBegin = src->fPathRef->verbsBegin(); const uint8_t* verbs = src->fPathRef->verbsEnd(); const SkPoint* pts = src->fPathRef->pointsEnd(); const SkScalar* conicWeights = src->fPathRef->conicWeightsEnd(); bool needMove = true; bool needClose = false; while (verbs > verbsBegin) { uint8_t v = *--verbs; int n = SkPathPriv::PtsInVerb(v); if (needMove) { --pts; this->moveTo(pts->fX, pts->fY); needMove = false; } pts -= n; switch (v) { case kMove_Verb: if (needClose) { this->close(); needClose = false; } needMove = true; pts += 1; // so we see the point in "if (needMove)" above break; case kLine_Verb: this->lineTo(pts[0]); break; case kQuad_Verb: this->quadTo(pts[1], pts[0]); break; case kConic_Verb: this->conicTo(pts[1], pts[0], *--conicWeights); break; case kCubic_Verb: this->cubicTo(pts[2], pts[1], pts[0]); break; case kClose_Verb: needClose = true; break; default: SkDEBUGFAIL("unexpected verb"); } } return *this; } /////////////////////////////////////////////////////////////////////////////// void SkPath::offset(SkScalar dx, SkScalar dy, SkPath* dst) const { SkMatrix matrix; matrix.setTranslate(dx, dy); this->transform(matrix, dst); } static void subdivide_cubic_to(SkPath* path, const SkPoint pts[4], int level = 2) { if (--level >= 0) { SkPoint tmp[7]; SkChopCubicAtHalf(pts, tmp); subdivide_cubic_to(path, &tmp[0], level); subdivide_cubic_to(path, &tmp[3], level); } else { path->cubicTo(pts[1], pts[2], pts[3]); } } void SkPath::transform(const SkMatrix& matrix, SkPath* dst, SkApplyPerspectiveClip pc) const { if (matrix.isIdentity()) { if (dst != nullptr && dst != this) { *dst = *this; } return; } SkDEBUGCODE(this->validate();) if (dst == nullptr) { dst = (SkPath*)this; } if (matrix.hasPerspective()) { SkPath tmp; tmp.fFillType = fFillType; SkPath clipped; const SkPath* src = this; if (pc == SkApplyPerspectiveClip::kYes && SkPathPriv::PerspectiveClip(*this, matrix, &clipped)) { src = &clipped; } SkPath::Iter iter(*src, false); SkPoint pts[4]; SkPath::Verb verb; while ((verb = iter.next(pts)) != kDone_Verb) { switch (verb) { case kMove_Verb: tmp.moveTo(pts[0]); break; case kLine_Verb: tmp.lineTo(pts[1]); break; case kQuad_Verb: // promote the quad to a conic tmp.conicTo(pts[1], pts[2], SkConic::TransformW(pts, SK_Scalar1, matrix)); break; case kConic_Verb: tmp.conicTo(pts[1], pts[2], SkConic::TransformW(pts, iter.conicWeight(), matrix)); break; case kCubic_Verb: subdivide_cubic_to(&tmp, pts); break; case kClose_Verb: tmp.close(); break; default: SkDEBUGFAIL("unknown verb"); break; } } dst->swap(tmp); SkPathRef::Editor ed(&dst->fPathRef); matrix.mapPoints(ed.writablePoints(), ed.pathRef()->countPoints()); dst->setFirstDirection(SkPathFirstDirection::kUnknown); } else { SkPathConvexity convexity = this->getConvexityOrUnknown(); SkPathRef::CreateTransformedCopy(&dst->fPathRef, *fPathRef, matrix); if (this != dst) { dst->fLastMoveToIndex = fLastMoveToIndex; dst->fFillType = fFillType; dst->fIsVolatile = fIsVolatile; } // Due to finite/fragile float numerics, we can't assume that a convex path remains // convex after a transformation, so mark it as unknown here. // However, some transformations are thought to be safe: // axis-aligned values under scale/translate. // if (convexity == SkPathConvexity::kConvex && (!matrix.isScaleTranslate() || !SkPathPriv::IsAxisAligned(*this))) { // Not safe to still assume we're convex... convexity = SkPathConvexity::kUnknown; } dst->setConvexity(convexity); if (this->getFirstDirection() == SkPathFirstDirection::kUnknown) { dst->setFirstDirection(SkPathFirstDirection::kUnknown); } else { SkScalar det2x2 = matrix.get(SkMatrix::kMScaleX) * matrix.get(SkMatrix::kMScaleY) - matrix.get(SkMatrix::kMSkewX) * matrix.get(SkMatrix::kMSkewY); if (det2x2 < 0) { dst->setFirstDirection( SkPathPriv::OppositeFirstDirection( (SkPathFirstDirection)this->getFirstDirection())); } else if (det2x2 > 0) { dst->setFirstDirection(this->getFirstDirection()); } else { dst->setFirstDirection(SkPathFirstDirection::kUnknown); } } SkDEBUGCODE(dst->validate();) } } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// SkPath::Iter::Iter() { #ifdef SK_DEBUG fPts = nullptr; fConicWeights = nullptr; fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0; fForceClose = fCloseLine = false; #endif // need to init enough to make next() harmlessly return kDone_Verb fVerbs = nullptr; fVerbStop = nullptr; fNeedClose = false; } SkPath::Iter::Iter(const SkPath& path, bool forceClose) { this->setPath(path, forceClose); } void SkPath::Iter::setPath(const SkPath& path, bool forceClose) { fPts = path.fPathRef->points(); fVerbs = path.fPathRef->verbsBegin(); fVerbStop = path.fPathRef->verbsEnd(); fConicWeights = path.fPathRef->conicWeights(); if (fConicWeights) { fConicWeights -= 1; // begin one behind } fLastPt.fX = fLastPt.fY = 0; fMoveTo.fX = fMoveTo.fY = 0; fForceClose = SkToU8(forceClose); fNeedClose = false; } bool SkPath::Iter::isClosedContour() const { if (fVerbs == nullptr || fVerbs == fVerbStop) { return false; } if (fForceClose) { return true; } const uint8_t* verbs = fVerbs; const uint8_t* stop = fVerbStop; if (kMove_Verb == *verbs) { verbs += 1; // skip the initial moveto } while (verbs < stop) { // verbs points one beyond the current verb, decrement first. unsigned v = *verbs++; if (kMove_Verb == v) { break; } if (kClose_Verb == v) { return true; } } return false; } SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) { SkASSERT(pts); if (fLastPt != fMoveTo) { // A special case: if both points are NaN, SkPoint::operation== returns // false, but the iterator expects that they are treated as the same. // (consider SkPoint is a 2-dimension float point). if (SkScalarIsNaN(fLastPt.fX) || SkScalarIsNaN(fLastPt.fY) || SkScalarIsNaN(fMoveTo.fX) || SkScalarIsNaN(fMoveTo.fY)) { return kClose_Verb; } pts[0] = fLastPt; pts[1] = fMoveTo; fLastPt = fMoveTo; fCloseLine = true; return kLine_Verb; } else { pts[0] = fMoveTo; return kClose_Verb; } } SkPath::Verb SkPath::Iter::next(SkPoint ptsParam[4]) { SkASSERT(ptsParam); if (fVerbs == fVerbStop) { // Close the curve if requested and if there is some curve to close if (fNeedClose) { if (kLine_Verb == this->autoClose(ptsParam)) { return kLine_Verb; } fNeedClose = false; return kClose_Verb; } return kDone_Verb; } unsigned verb = *fVerbs++; const SkPoint* SK_RESTRICT srcPts = fPts; SkPoint* SK_RESTRICT pts = ptsParam; switch (verb) { case kMove_Verb: if (fNeedClose) { fVerbs--; // move back one verb verb = this->autoClose(pts); if (verb == kClose_Verb) { fNeedClose = false; } return (Verb)verb; } if (fVerbs == fVerbStop) { // might be a trailing moveto return kDone_Verb; } fMoveTo = *srcPts; pts[0] = *srcPts; srcPts += 1; fLastPt = fMoveTo; fNeedClose = fForceClose; break; case kLine_Verb: pts[0] = fLastPt; pts[1] = srcPts[0]; fLastPt = srcPts[0]; fCloseLine = false; srcPts += 1; break; case kConic_Verb: fConicWeights += 1; [[fallthrough]]; case kQuad_Verb: pts[0] = fLastPt; memcpy(&pts[1], srcPts, 2 * sizeof(SkPoint)); fLastPt = srcPts[1]; srcPts += 2; break; case kCubic_Verb: pts[0] = fLastPt; memcpy(&pts[1], srcPts, 3 * sizeof(SkPoint)); fLastPt = srcPts[2]; srcPts += 3; break; case kClose_Verb: verb = this->autoClose(pts); if (verb == kLine_Verb) { fVerbs--; // move back one verb } else { fNeedClose = false; } fLastPt = fMoveTo; break; } fPts = srcPts; return (Verb)verb; } void SkPath::RawIter::setPath(const SkPath& path) { SkPathPriv::Iterate iterate(path); fIter = iterate.begin(); fEnd = iterate.end(); } SkPath::Verb SkPath::RawIter::next(SkPoint pts[4]) { if (!(fIter != fEnd)) { return kDone_Verb; } auto [verb, iterPts, weights] = *fIter; int numPts; switch (verb) { case SkPathVerb::kMove: numPts = 1; break; case SkPathVerb::kLine: numPts = 2; break; case SkPathVerb::kQuad: numPts = 3; break; case SkPathVerb::kConic: numPts = 3; fConicWeight = *weights; break; case SkPathVerb::kCubic: numPts = 4; break; case SkPathVerb::kClose: numPts = 0; break; } memcpy(pts, iterPts, sizeof(SkPoint) * numPts); ++fIter; return (Verb) verb; } /////////////////////////////////////////////////////////////////////////////// static void append_params(SkString* str, const char label[], const SkPoint pts[], int count, SkScalarAsStringType strType, SkScalar conicWeight = -12345) { str->append(label); str->append("("); const SkScalar* values = &pts[0].fX; count *= 2; for (int i = 0; i < count; ++i) { SkAppendScalar(str, values[i], strType); if (i < count - 1) { str->append(", "); } } if (conicWeight != -12345) { str->append(", "); SkAppendScalar(str, conicWeight, strType); } str->append(");"); if (kHex_SkScalarAsStringType == strType) { str->append(" // "); for (int i = 0; i < count; ++i) { SkAppendScalarDec(str, values[i]); if (i < count - 1) { str->append(", "); } } if (conicWeight >= 0) { str->append(", "); SkAppendScalarDec(str, conicWeight); } } str->append("\n"); } void SkPath::dump(SkWStream* wStream, bool dumpAsHex) const { SkScalarAsStringType asType = dumpAsHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType; Iter iter(*this, false); SkPoint pts[4]; Verb verb; SkString builder; char const * const gFillTypeStrs[] = { "Winding", "EvenOdd", "InverseWinding", "InverseEvenOdd", }; builder.printf("path.setFillType(SkPathFillType::k%s);\n", gFillTypeStrs[(int) this->getFillType()]); while ((verb = iter.next(pts)) != kDone_Verb) { switch (verb) { case kMove_Verb: append_params(&builder, "path.moveTo", &pts[0], 1, asType); break; case kLine_Verb: append_params(&builder, "path.lineTo", &pts[1], 1, asType); break; case kQuad_Verb: append_params(&builder, "path.quadTo", &pts[1], 2, asType); break; case kConic_Verb: append_params(&builder, "path.conicTo", &pts[1], 2, asType, iter.conicWeight()); break; case kCubic_Verb: append_params(&builder, "path.cubicTo", &pts[1], 3, asType); break; case kClose_Verb: builder.append("path.close();\n"); break; default: SkDebugf(" path: UNKNOWN VERB %d, aborting dump...\n", verb); verb = kDone_Verb; // stop the loop break; } if (!wStream && builder.size()) { SkDebugf("%s", builder.c_str()); builder.reset(); } } if (wStream) { wStream->writeText(builder.c_str()); } } void SkPath::dumpArrays(SkWStream* wStream, bool dumpAsHex) const { SkString builder; auto bool_str = [](bool v) { return v ? "true" : "false"; }; builder.appendf("// fBoundsIsDirty = %s\n", bool_str(fPathRef->fBoundsIsDirty)); builder.appendf("// fGenerationID = %d\n", fPathRef->fGenerationID); builder.appendf("// fSegmentMask = %d\n", fPathRef->fSegmentMask); builder.appendf("// fIsOval = %s\n", bool_str(fPathRef->fIsOval)); builder.appendf("// fIsRRect = %s\n", bool_str(fPathRef->fIsRRect)); auto append_scalar = [&](SkScalar v) { if (dumpAsHex) { builder.appendf("SkBits2Float(0x%08X) /* %g */", SkFloat2Bits(v), v); } else { builder.appendf("%g", v); } }; builder.append("const SkPoint path_points[] = {\n"); for (int i = 0; i < this->countPoints(); ++i) { SkPoint p = this->getPoint(i); builder.append(" { "); append_scalar(p.fX); builder.append(", "); append_scalar(p.fY); builder.append(" },\n"); } builder.append("};\n"); const char* gVerbStrs[] = { "Move", "Line", "Quad", "Conic", "Cubic", "Close" }; builder.append("const uint8_t path_verbs[] = {\n "); for (auto v = fPathRef->verbsBegin(); v != fPathRef->verbsEnd(); ++v) { builder.appendf("(uint8_t)SkPathVerb::k%s, ", gVerbStrs[*v]); } builder.append("\n};\n"); const int nConics = fPathRef->conicWeightsEnd() - fPathRef->conicWeights(); if (nConics) { builder.append("const SkScalar path_conics[] = {\n "); for (auto c = fPathRef->conicWeights(); c != fPathRef->conicWeightsEnd(); ++c) { append_scalar(*c); builder.append(", "); } builder.append("\n};\n"); } char const * const gFillTypeStrs[] = { "Winding", "EvenOdd", "InverseWinding", "InverseEvenOdd", }; builder.appendf("SkPath path = SkPath::Make(path_points, %d, path_verbs, %d, %s, %d,\n", this->countPoints(), this->countVerbs(), nConics ? "path_conics" : "nullptr", nConics); builder.appendf(" SkPathFillType::k%s, %s);\n", gFillTypeStrs[(int)this->getFillType()], bool_str(fIsVolatile)); if (wStream) { wStream->writeText(builder.c_str()); } else { SkDebugf("%s\n", builder.c_str()); } } bool SkPath::isValidImpl() const { if ((fFillType & ~3) != 0) { return false; } #ifdef SK_DEBUG_PATH if (!fBoundsIsDirty) { SkRect bounds; bool isFinite = compute_pt_bounds(&bounds, *fPathRef.get()); if (SkToBool(fIsFinite) != isFinite) { return false; } if (fPathRef->countPoints() <= 1) { // if we're empty, fBounds may be empty but translated, so we can't // necessarily compare to bounds directly // try path.addOval(2, 2, 2, 2) which is empty, but the bounds will // be [2, 2, 2, 2] if (!bounds.isEmpty() || !fBounds.isEmpty()) { return false; } } else { if (bounds.isEmpty()) { if (!fBounds.isEmpty()) { return false; } } else { if (!fBounds.isEmpty()) { if (!fBounds.contains(bounds)) { return false; } } } } } #endif // SK_DEBUG_PATH return true; } /////////////////////////////////////////////////////////////////////////////// static int sign(SkScalar x) { return x < 0; } #define kValueNeverReturnedBySign 2 enum DirChange { kUnknown_DirChange, kLeft_DirChange, kRight_DirChange, kStraight_DirChange, kBackwards_DirChange, // if double back, allow simple lines to be convex kInvalid_DirChange }; // only valid for a single contour struct Convexicator { /** The direction returned is only valid if the path is determined convex */ SkPathFirstDirection getFirstDirection() const { return fFirstDirection; } void setMovePt(const SkPoint& pt) { fFirstPt = fLastPt = pt; fExpectedDir = kInvalid_DirChange; } bool addPt(const SkPoint& pt) { if (fLastPt == pt) { return true; } // should only be true for first non-zero vector after setMovePt was called. if (fFirstPt == fLastPt && fExpectedDir == kInvalid_DirChange) { fLastVec = pt - fLastPt; fFirstVec = fLastVec; } else if (!this->addVec(pt - fLastPt)) { return false; } fLastPt = pt; return true; } static SkPathConvexity BySign(const SkPoint points[], int count) { if (count <= 3) { // point, line, or triangle are always convex return SkPathConvexity::kConvex; } const SkPoint* last = points + count; SkPoint currPt = *points++; SkPoint firstPt = currPt; int dxes = 0; int dyes = 0; int lastSx = kValueNeverReturnedBySign; int lastSy = kValueNeverReturnedBySign; for (int outerLoop = 0; outerLoop < 2; ++outerLoop ) { while (points != last) { SkVector vec = *points - currPt; if (!vec.isZero()) { // give up if vector construction failed if (!vec.isFinite()) { return SkPathConvexity::kUnknown; } int sx = sign(vec.fX); int sy = sign(vec.fY); dxes += (sx != lastSx); dyes += (sy != lastSy); if (dxes > 3 || dyes > 3) { return SkPathConvexity::kConcave; } lastSx = sx; lastSy = sy; } currPt = *points++; if (outerLoop) { break; } } points = &firstPt; } return SkPathConvexity::kConvex; // that is, it may be convex, don't know yet } bool close() { // If this was an explicit close, there was already a lineTo to fFirstPoint, so this // addPt() is a no-op. Otherwise, the addPt implicitly closes the contour. In either case, // we have to check the direction change along the first vector in case it is concave. return this->addPt(fFirstPt) && this->addVec(fFirstVec); } bool isFinite() const { return fIsFinite; } int reversals() const { return fReversals; } private: DirChange directionChange(const SkVector& curVec) { SkScalar cross = SkPoint::CrossProduct(fLastVec, curVec); if (!SkScalarIsFinite(cross)) { return kUnknown_DirChange; } if (cross == 0) { return fLastVec.dot(curVec) < 0 ? kBackwards_DirChange : kStraight_DirChange; } return 1 == SkScalarSignAsInt(cross) ? kRight_DirChange : kLeft_DirChange; } bool addVec(const SkVector& curVec) { DirChange dir = this->directionChange(curVec); switch (dir) { case kLeft_DirChange: // fall through case kRight_DirChange: if (kInvalid_DirChange == fExpectedDir) { fExpectedDir = dir; fFirstDirection = (kRight_DirChange == dir) ? SkPathFirstDirection::kCW : SkPathFirstDirection::kCCW; } else if (dir != fExpectedDir) { fFirstDirection = SkPathFirstDirection::kUnknown; return false; } fLastVec = curVec; break; case kStraight_DirChange: break; case kBackwards_DirChange: // allow path to reverse direction twice // Given path.moveTo(0, 0); path.lineTo(1, 1); // - 1st reversal: direction change formed by line (0,0 1,1), line (1,1 0,0) // - 2nd reversal: direction change formed by line (1,1 0,0), line (0,0 1,1) fLastVec = curVec; return ++fReversals < 3; case kUnknown_DirChange: return (fIsFinite = false); case kInvalid_DirChange: SK_ABORT("Use of invalid direction change flag"); break; } return true; } SkPoint fFirstPt {0, 0}; // The first point of the contour, e.g. moveTo(x,y) SkVector fFirstVec {0, 0}; // The direction leaving fFirstPt to the next vertex SkPoint fLastPt {0, 0}; // The last point passed to addPt() SkVector fLastVec {0, 0}; // The direction that brought the path to fLastPt DirChange fExpectedDir { kInvalid_DirChange }; SkPathFirstDirection fFirstDirection { SkPathFirstDirection::kUnknown }; int fReversals { 0 }; bool fIsFinite { true }; }; SkPathConvexity SkPath::computeConvexity() const { auto setComputedConvexity = [=, this](SkPathConvexity convexity){ SkASSERT(SkPathConvexity::kUnknown != convexity); this->setConvexity(convexity); return convexity; }; auto setFail = [=](){ return setComputedConvexity(SkPathConvexity::kConcave); }; if (!this->isFinite()) { return setFail(); } // pointCount potentially includes a block of leading moveTos and trailing moveTos. Convexity // only cares about the last of the initial moveTos and the verbs before the final moveTos. int pointCount = this->countPoints(); int skipCount = SkPathPriv::LeadingMoveToCount(*this) - 1; if (fLastMoveToIndex >= 0) { if (fLastMoveToIndex == pointCount - 1) { // Find the last real verb that affects convexity auto verbs = fPathRef->verbsEnd() - 1; while(verbs > fPathRef->verbsBegin() && *verbs == Verb::kMove_Verb) { verbs--; pointCount--; } } else if (fLastMoveToIndex != skipCount) { // There's an additional moveTo skPath_between two blocks of other verbs, so the path must have // more than one contour and cannot be convex. return setComputedConvexity(SkPathConvexity::kConcave); } // else no trailing or intermediate moveTos to worry about } const SkPoint* points = fPathRef->points(); if (skipCount > 0) { points += skipCount; pointCount -= skipCount; } // Check to see if path changes direction more than three times as quick concave test SkPathConvexity convexity = Convexicator::BySign(points, pointCount); if (SkPathConvexity::kConvex != convexity) { return setComputedConvexity(SkPathConvexity::kConcave); } int contourCount = 0; bool needsClose = false; Convexicator state; for (auto [verb, pts, wt] : SkPathPriv::Iterate(*this)) { // Looking for the last moveTo before non-move verbs start if (contourCount == 0) { if (verb == SkPathVerb::kMove) { state.setMovePt(pts[0]); } else { // Starting the actual contour, fall through to c=1 to add the points contourCount++; needsClose = true; } } // Accumulating points into the Convexicator until we hit a close or another move if (contourCount == 1) { if (verb == SkPathVerb::kClose || verb == SkPathVerb::kMove) { if (!state.close()) { return setFail(); } needsClose = false; contourCount++; } else { // lines add 1 point, cubics add 3, conics and quads add 2 int count = SkPathPriv::PtsInVerb((unsigned) verb); SkASSERT(count > 0); for (int i = 1; i <= count; ++i) { if (!state.addPt(pts[i])) { return setFail(); } } } } else { // The first contour has closed and anything other than spurious trailing moves means // there's multiple contours and the path can't be convex if (verb != SkPathVerb::kMove) { return setFail(); } } } // If the path isn't explicitly closed do so implicitly if (needsClose && !state.close()) { return setFail(); } if (this->getFirstDirection() == SkPathFirstDirection::kUnknown) { if (state.getFirstDirection() == SkPathFirstDirection::kUnknown && !this->getBounds().isEmpty()) { return setComputedConvexity(state.reversals() < 3 ? SkPathConvexity::kConvex : SkPathConvexity::kConcave); } this->setFirstDirection(state.getFirstDirection()); } return setComputedConvexity(SkPathConvexity::kConvex); } /////////////////////////////////////////////////////////////////////////////// class ContourIter { public: ContourIter(const SkPathRef& pathRef); bool done() const { return fDone; } // if !done() then these may be called int count() const { return fCurrPtCount; } const SkPoint* pts() const { return fCurrPt; } void next(); private: int fCurrPtCount; const SkPoint* fCurrPt; const uint8_t* fCurrVerb; const uint8_t* fStopVerbs; const SkScalar* fCurrConicWeight; bool fDone; SkDEBUGCODE(int fContourCounter;) }; ContourIter::ContourIter(const SkPathRef& pathRef) { fStopVerbs = pathRef.verbsEnd(); fDone = false; fCurrPt = pathRef.points(); fCurrVerb = pathRef.verbsBegin(); fCurrConicWeight = pathRef.conicWeights(); fCurrPtCount = 0; SkDEBUGCODE(fContourCounter = 0;) this->next(); } void ContourIter::next() { if (fCurrVerb >= fStopVerbs) { fDone = true; } if (fDone) { return; } // skip pts of prev contour fCurrPt += fCurrPtCount; SkASSERT(SkPath::kMove_Verb == fCurrVerb[0]); int ptCount = 1; // moveTo const uint8_t* verbs = fCurrVerb; for (verbs++; verbs < fStopVerbs; verbs++) { switch (*verbs) { case SkPath::kMove_Verb: goto CONTOUR_END; case SkPath::kLine_Verb: ptCount += 1; break; case SkPath::kConic_Verb: fCurrConicWeight += 1; [[fallthrough]]; case SkPath::kQuad_Verb: ptCount += 2; break; case SkPath::kCubic_Verb: ptCount += 3; break; case SkPath::kClose_Verb: break; default: SkDEBUGFAIL("unexpected verb"); break; } } CONTOUR_END: fCurrPtCount = ptCount; fCurrVerb = verbs; SkDEBUGCODE(++fContourCounter;) } // returns cross product of (p1 - p0) and (p2 - p0) static SkScalar cross_prod(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2) { SkScalar cross = SkPoint::CrossProduct(p1 - p0, p2 - p0); // We may get 0 when the above subtracts underflow. We expect this to be // very rare and lazily promote to double. if (0 == cross) { double p0x = SkScalarToDouble(p0.fX); double p0y = SkScalarToDouble(p0.fY); double p1x = SkScalarToDouble(p1.fX); double p1y = SkScalarToDouble(p1.fY); double p2x = SkScalarToDouble(p2.fX); double p2y = SkScalarToDouble(p2.fY); cross = SkDoubleToScalar((p1x - p0x) * (p2y - p0y) - (p1y - p0y) * (p2x - p0x)); } return cross; } // Returns the first pt with the maximum Y coordinate static int find_max_y(const SkPoint pts[], int count) { SkASSERT(count > 0); SkScalar max = pts[0].fY; int firstIndex = 0; for (int i = 1; i < count; ++i) { SkScalar y = pts[i].fY; if (y > max) { max = y; firstIndex = i; } } return firstIndex; } static int find_diff_pt(const SkPoint pts[], int index, int n, int inc) { int i = index; for (;;) { i = (i + inc) % n; if (i == index) { // we wrapped around, so abort break; } if (pts[index] != pts[i]) { // found a different point, success! break; } } return i; } /** * Starting at index, and moving forward (incrementing), find the xmin and * xmax of the contiguous points that have the same Y. */ static int find_min_max_x_at_y(const SkPoint pts[], int index, int n, int* maxIndexPtr) { const SkScalar y = pts[index].fY; SkScalar min = pts[index].fX; SkScalar max = min; int minIndex = index; int maxIndex = index; for (int i = index + 1; i < n; ++i) { if (pts[i].fY != y) { break; } SkScalar x = pts[i].fX; if (x < min) { min = x; minIndex = i; } else if (x > max) { max = x; maxIndex = i; } } *maxIndexPtr = maxIndex; return minIndex; } static SkPathFirstDirection crossToDir(SkScalar cross) { return cross > 0 ? SkPathFirstDirection::kCW : SkPathFirstDirection::kCCW; } /* * We loop through all contours, and keep the computed cross-product of the * contour that contained the global y-max. If we just look at the first * contour, we may find one that is wound the opposite way (correctly) since * it is the interior of a hole (e.g. 'o'). Thus we must find the contour * that is outer most (or at least has the global y-max) before we can consider * its cross product. */ SkPathFirstDirection SkPathPriv::ComputeFirstDirection(const SkPath& path) { auto d = path.getFirstDirection(); if (d != SkPathFirstDirection::kUnknown) { return d; } // We don't want to pay the cost for computing convexity if it is unknown, // so we call getConvexityOrUnknown() instead of isConvex(). if (path.getConvexityOrUnknown() == SkPathConvexity::kConvex) { SkASSERT(d == SkPathFirstDirection::kUnknown); return d; } ContourIter iter(*path.fPathRef); // initialize with our logical y-min SkScalar ymax = path.getBounds().fTop; SkScalar ymaxCross = 0; for (; !iter.done(); iter.next()) { int n = iter.count(); if (n < 3) { continue; } const SkPoint* pts = iter.pts(); SkScalar cross = 0; int index = find_max_y(pts, n); if (pts[index].fY < ymax) { continue; } // If there is more than 1 distinct point at the y-max, we take the // x-min and x-max of them and just subtract to compute the dir. if (pts[(index + 1) % n].fY == pts[index].fY) { int maxIndex; int minIndex = find_min_max_x_at_y(pts, index, n, &maxIndex); if (minIndex == maxIndex) { goto TRY_CROSSPROD; } SkASSERT(pts[minIndex].fY == pts[index].fY); SkASSERT(pts[maxIndex].fY == pts[index].fY); SkASSERT(pts[minIndex].fX <= pts[maxIndex].fX); // we just subtract the indices, and let that auto-convert to // SkScalar, since we just want - or + to signal the direction. cross = minIndex - maxIndex; } else { TRY_CROSSPROD: // Find a next and prev index to use for the cross-product test, // but we try to find pts that form non-zero vectors from pts[index] // // Its possible that we can't find two non-degenerate vectors, so // we have to guard our search (e.g. all the pts could be in the // same place). // we pass n - 1 instead of -1 so we don't foul up % operator by // passing it a negative LH argument. int prev = find_diff_pt(pts, index, n, n - 1); if (prev == index) { // completely degenerate, skip to next contour continue; } int next = find_diff_pt(pts, index, n, 1); SkASSERT(next != index); cross = cross_prod(pts[prev], pts[index], pts[next]); // if we get a zero and the points are horizontal, then we look at the spread in // x-direction. We really should continue to walk away from the degeneracy until // there is a divergence. if (0 == cross && pts[prev].fY == pts[index].fY && pts[next].fY == pts[index].fY) { // construct the subtract so we get the correct Direction below cross = pts[index].fX - pts[next].fX; } } if (cross) { // record our best guess so far ymax = pts[index].fY; ymaxCross = cross; } } if (ymaxCross) { d = crossToDir(ymaxCross); path.setFirstDirection(d); } return d; // may still be kUnknown } /////////////////////////////////////////////////////////////////////////////// static bool skPath_between(SkScalar a, SkScalar b, SkScalar c) { SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0) || (SkScalarNearlyZero(a) && SkScalarNearlyZero(b) && SkScalarNearlyZero(c))); return (a - b) * (c - b) <= 0; } static SkScalar eval_cubic_pts(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3, SkScalar t) { SkScalar A = c3 + 3*(c1 - c2) - c0; SkScalar B = 3*(c2 - c1 - c1 + c0); SkScalar C = 3*(c1 - c0); SkScalar D = c0; return poly_eval(A, B, C, D, t); } template static void find_minmax(const SkPoint pts[], SkScalar* minPtr, SkScalar* maxPtr) { SkScalar min, max; min = max = pts[0].fX; for (size_t i = 1; i < N; ++i) { min = std::min(min, pts[i].fX); max = std::max(max, pts[i].fX); } *minPtr = min; *maxPtr = max; } static bool checkOnCurve(SkScalar x, SkScalar y, const SkPoint& start, const SkPoint& end) { if (start.fY == end.fY) { return skPath_between(start.fX, x, end.fX) && x != end.fX; } else { return x == start.fX && y == start.fY; } } static int winding_mono_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { SkScalar y0 = pts[0].fY; SkScalar y3 = pts[3].fY; int dir = 1; if (y0 > y3) { using std::swap; swap(y0, y3); dir = -1; } if (y < y0 || y > y3) { return 0; } if (checkOnCurve(x, y, pts[0], pts[3])) { *onCurveCount += 1; return 0; } if (y == y3) { return 0; } // quickreject or quickaccept SkScalar min, max; find_minmax<4>(pts, &min, &max); if (x < min) { return 0; } if (x > max) { return dir; } // compute the actual x(t) value SkScalar t; if (!SkCubicClipper::ChopMonoAtY(pts, y, &t)) { return 0; } SkScalar xt = eval_cubic_pts(pts[0].fX, pts[1].fX, pts[2].fX, pts[3].fX, t); if (SkScalarNearlyEqual(xt, x)) { if (x != pts[3].fX || y != pts[3].fY) { // don't test end points; they're start points *onCurveCount += 1; return 0; } } return xt < x ? dir : 0; } static int winding_cubic(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { SkPoint dst[10]; int n = SkChopCubicAtYExtrema(pts, dst); int w = 0; for (int i = 0; i <= n; ++i) { w += winding_mono_cubic(&dst[i * 3], x, y, onCurveCount); } return w; } static double conic_eval_numerator(const SkScalar src[], SkScalar w, SkScalar t) { SkASSERT(src); SkASSERT(t >= 0 && t <= 1); SkScalar src2w = src[2] * w; SkScalar C = src[0]; SkScalar A = src[4] - 2 * src2w + C; SkScalar B = 2 * (src2w - C); return poly_eval(A, B, C, t); } static double conic_eval_denominator(SkScalar w, SkScalar t) { SkScalar B = 2 * (w - 1); SkScalar C = 1; SkScalar A = -B; return poly_eval(A, B, C, t); } static int winding_mono_conic(const SkConic& conic, SkScalar x, SkScalar y, int* onCurveCount) { const SkPoint* pts = conic.fPts; SkScalar y0 = pts[0].fY; SkScalar y2 = pts[2].fY; int dir = 1; if (y0 > y2) { using std::swap; swap(y0, y2); dir = -1; } if (y < y0 || y > y2) { return 0; } if (checkOnCurve(x, y, pts[0], pts[2])) { *onCurveCount += 1; return 0; } if (y == y2) { return 0; } SkScalar roots[2]; SkScalar A = pts[2].fY; SkScalar B = pts[1].fY * conic.fW - y * conic.fW + y; SkScalar C = pts[0].fY; A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept) B -= C; // B = b*w - w * yCept + yCept - a C -= y; int n = SkFindUnitQuadRoots(A, 2 * B, C, roots); SkASSERT(n <= 1); SkScalar xt; if (0 == n) { // zero roots are returned only when y0 == y // Need [0] if dir == 1 // and [2] if dir == -1 xt = pts[1 - dir].fX; } else { SkScalar t = roots[0]; xt = conic_eval_numerator(&pts[0].fX, conic.fW, t) / conic_eval_denominator(conic.fW, t); } if (SkScalarNearlyEqual(xt, x)) { if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points *onCurveCount += 1; return 0; } } return xt < x ? dir : 0; } static bool is_mono_quad(SkScalar y0, SkScalar y1, SkScalar y2) { // return SkScalarSignAsInt(y0 - y1) + SkScalarSignAsInt(y1 - y2) != 0; if (y0 == y1) { return true; } if (y0 < y1) { return y1 <= y2; } else { return y1 >= y2; } } static int winding_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar weight, int* onCurveCount) { SkConic conic(pts, weight); SkConic chopped[2]; // If the data points are very large, the conic may not be monotonic but may also // fail to chop. Then, the chopper does not split the original conic in two. bool isMono = is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY) || !conic.chopAtYExtrema(chopped); int w = winding_mono_conic(isMono ? conic : chopped[0], x, y, onCurveCount); if (!isMono) { w += winding_mono_conic(chopped[1], x, y, onCurveCount); } return w; } static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { SkScalar y0 = pts[0].fY; SkScalar y2 = pts[2].fY; int dir = 1; if (y0 > y2) { using std::swap; swap(y0, y2); dir = -1; } if (y < y0 || y > y2) { return 0; } if (checkOnCurve(x, y, pts[0], pts[2])) { *onCurveCount += 1; return 0; } if (y == y2) { return 0; } // bounds check on X (not required. is it faster?) #if 0 if (pts[0].fX > x && pts[1].fX > x && pts[2].fX > x) { return 0; } #endif SkScalar roots[2]; int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY, 2 * (pts[1].fY - pts[0].fY), pts[0].fY - y, roots); SkASSERT(n <= 1); SkScalar xt; if (0 == n) { // zero roots are returned only when y0 == y // Need [0] if dir == 1 // and [2] if dir == -1 xt = pts[1 - dir].fX; } else { SkScalar t = roots[0]; SkScalar C = pts[0].fX; SkScalar A = pts[2].fX - 2 * pts[1].fX + C; SkScalar B = 2 * (pts[1].fX - C); xt = poly_eval(A, B, C, t); } if (SkScalarNearlyEqual(xt, x)) { if (x != pts[2].fX || y != pts[2].fY) { // don't test end points; they're start points *onCurveCount += 1; return 0; } } return xt < x ? dir : 0; } static int winding_quad(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { SkPoint dst[5]; int n = 0; if (!is_mono_quad(pts[0].fY, pts[1].fY, pts[2].fY)) { n = SkChopQuadAtYExtrema(pts, dst); pts = dst; } int w = winding_mono_quad(pts, x, y, onCurveCount); if (n > 0) { w += winding_mono_quad(&pts[2], x, y, onCurveCount); } return w; } static int winding_line(const SkPoint pts[], SkScalar x, SkScalar y, int* onCurveCount) { SkScalar x0 = pts[0].fX; SkScalar y0 = pts[0].fY; SkScalar x1 = pts[1].fX; SkScalar y1 = pts[1].fY; SkScalar dy = y1 - y0; int dir = 1; if (y0 > y1) { using std::swap; swap(y0, y1); dir = -1; } if (y < y0 || y > y1) { return 0; } if (checkOnCurve(x, y, pts[0], pts[1])) { *onCurveCount += 1; return 0; } if (y == y1) { return 0; } SkScalar cross = (x1 - x0) * (y - pts[0].fY) - dy * (x - x0); if (!cross) { // zero cross means the point is on the line, and since the case where // y of the query point is at the end point is handled above, we can be // sure that we're on the line (excluding the end point) here if (x != x1 || y != pts[1].fY) { *onCurveCount += 1; } dir = 0; } else if (SkScalarSignAsInt(cross) == dir) { dir = 0; } return dir; } static void tangent_cubic(const SkPoint pts[], SkScalar x, SkScalar y, SkTDArray* tangents) { if (!skPath_between(pts[0].fY, y, pts[1].fY) && !skPath_between(pts[1].fY, y, pts[2].fY) && !skPath_between(pts[2].fY, y, pts[3].fY)) { return; } if (!skPath_between(pts[0].fX, x, pts[1].fX) && !skPath_between(pts[1].fX, x, pts[2].fX) && !skPath_between(pts[2].fX, x, pts[3].fX)) { return; } SkPoint dst[10]; int n = SkChopCubicAtYExtrema(pts, dst); for (int i = 0; i <= n; ++i) { SkPoint* c = &dst[i * 3]; SkScalar t; if (!SkCubicClipper::ChopMonoAtY(c, y, &t)) { continue; } SkScalar xt = eval_cubic_pts(c[0].fX, c[1].fX, c[2].fX, c[3].fX, t); if (!SkScalarNearlyEqual(x, xt)) { continue; } SkVector tangent; SkEvalCubicAt(c, t, nullptr, &tangent, nullptr); tangents->push_back(tangent); } } static void tangent_conic(const SkPoint pts[], SkScalar x, SkScalar y, SkScalar w, SkTDArray* tangents) { if (!skPath_between(pts[0].fY, y, pts[1].fY) && !skPath_between(pts[1].fY, y, pts[2].fY)) { return; } if (!skPath_between(pts[0].fX, x, pts[1].fX) && !skPath_between(pts[1].fX, x, pts[2].fX)) { return; } SkScalar roots[2]; SkScalar A = pts[2].fY; SkScalar B = pts[1].fY * w - y * w + y; SkScalar C = pts[0].fY; A += C - 2 * B; // A = a + c - 2*(b*w - yCept*w + yCept) B -= C; // B = b*w - w * yCept + yCept - a C -= y; int n = SkFindUnitQuadRoots(A, 2 * B, C, roots); for (int index = 0; index < n; ++index) { SkScalar t = roots[index]; SkScalar xt = conic_eval_numerator(&pts[0].fX, w, t) / conic_eval_denominator(w, t); if (!SkScalarNearlyEqual(x, xt)) { continue; } SkConic conic(pts, w); tangents->push_back(conic.evalTangentAt(t)); } } static void tangent_quad(const SkPoint pts[], SkScalar x, SkScalar y, SkTDArray* tangents) { if (!skPath_between(pts[0].fY, y, pts[1].fY) && !skPath_between(pts[1].fY, y, pts[2].fY)) { return; } if (!skPath_between(pts[0].fX, x, pts[1].fX) && !skPath_between(pts[1].fX, x, pts[2].fX)) { return; } SkScalar roots[2]; int n = SkFindUnitQuadRoots(pts[0].fY - 2 * pts[1].fY + pts[2].fY, 2 * (pts[1].fY - pts[0].fY), pts[0].fY - y, roots); for (int index = 0; index < n; ++index) { SkScalar t = roots[index]; SkScalar C = pts[0].fX; SkScalar A = pts[2].fX - 2 * pts[1].fX + C; SkScalar B = 2 * (pts[1].fX - C); SkScalar xt = poly_eval(A, B, C, t); if (!SkScalarNearlyEqual(x, xt)) { continue; } tangents->push_back(SkEvalQuadTangentAt(pts, t)); } } static void tangent_line(const SkPoint pts[], SkScalar x, SkScalar y, SkTDArray* tangents) { SkScalar y0 = pts[0].fY; SkScalar y1 = pts[1].fY; if (!skPath_between(y0, y, y1)) { return; } SkScalar x0 = pts[0].fX; SkScalar x1 = pts[1].fX; if (!skPath_between(x0, x, x1)) { return; } SkScalar dx = x1 - x0; SkScalar dy = y1 - y0; if (!SkScalarNearlyEqual((x - x0) * dy, dx * (y - y0))) { return; } SkVector v; v.set(dx, dy); tangents->push_back(v); } static bool contains_inclusive(const SkRect& r, SkScalar x, SkScalar y) { return r.fLeft <= x && x <= r.fRight && r.fTop <= y && y <= r.fBottom; } bool SkPath::contains(SkScalar x, SkScalar y) const { bool isInverse = this->isInverseFillType(); if (this->isEmpty()) { return isInverse; } if (!contains_inclusive(this->getBounds(), x, y)) { return isInverse; } SkPath::Iter iter(*this, true); bool done = false; int w = 0; int onCurveCount = 0; do { SkPoint pts[4]; switch (iter.next(pts)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb: w += winding_line(pts, x, y, &onCurveCount); break; case SkPath::kQuad_Verb: w += winding_quad(pts, x, y, &onCurveCount); break; case SkPath::kConic_Verb: w += winding_conic(pts, x, y, iter.conicWeight(), &onCurveCount); break; case SkPath::kCubic_Verb: w += winding_cubic(pts, x, y, &onCurveCount); break; case SkPath::kDone_Verb: done = true; break; } } while (!done); bool evenOddFill = SkPathFillType::kEvenOdd == this->getFillType() || SkPathFillType::kInverseEvenOdd == this->getFillType(); if (evenOddFill) { w &= 1; } if (w) { return !isInverse; } if (onCurveCount <= 1) { return SkToBool(onCurveCount) ^ isInverse; } if ((onCurveCount & 1) || evenOddFill) { return SkToBool(onCurveCount & 1) ^ isInverse; } // If the point touches an even number of curves, and the fill is winding, check for // coincidence. Count coincidence as places where the on curve points have identical tangents. iter.setPath(*this, true); done = false; SkTDArray tangents; do { SkPoint pts[4]; int oldCount = tangents.size(); switch (iter.next(pts)) { case SkPath::kMove_Verb: case SkPath::kClose_Verb: break; case SkPath::kLine_Verb: tangent_line(pts, x, y, &tangents); break; case SkPath::kQuad_Verb: tangent_quad(pts, x, y, &tangents); break; case SkPath::kConic_Verb: tangent_conic(pts, x, y, iter.conicWeight(), &tangents); break; case SkPath::kCubic_Verb: tangent_cubic(pts, x, y, &tangents); break; case SkPath::kDone_Verb: done = true; break; } if (tangents.size() > oldCount) { int last = tangents.size() - 1; const SkVector& tangent = tangents[last]; if (SkScalarNearlyZero(SkPointPriv::LengthSqd(tangent))) { tangents.remove(last); } else { for (int index = 0; index < last; ++index) { const SkVector& test = tangents[index]; if (SkScalarNearlyZero(test.cross(tangent)) && SkScalarSignAsInt(tangent.fX * test.fX) <= 0 && SkScalarSignAsInt(tangent.fY * test.fY) <= 0) { tangents.remove(last); tangents.removeShuffle(index); break; } } } } } while (!done); return SkToBool(tangents.size()) ^ isInverse; } // Sort of like makeSpace(0) but the the additional requirement that we actively shrink the // allocations to just fit the current needs. makeSpace() will only grow, but never shrinks. // void SkPath::shrinkToFit() { // Since this can relocate the allocated arrays, we have to defensively copy ourselves if // we're not the only owner of the pathref... since relocating the arrays will invalidate // any existing iterators. if (!fPathRef->unique()) { SkPathRef* pr = new SkPathRef; pr->copy(*fPathRef, 0, 0); fPathRef.reset(pr); } fPathRef->fPoints.shrink_to_fit(); fPathRef->fVerbs.shrink_to_fit(); fPathRef->fConicWeights.shrink_to_fit(); SkDEBUGCODE(fPathRef->validate();) } int SkPath::ConvertConicToQuads(const SkPoint& p0, const SkPoint& p1, const SkPoint& p2, SkScalar w, SkPoint pts[], int pow2) { const SkConic conic(p0, p1, p2, w); return conic.chopIntoQuadsPOW2(pts, pow2); } bool SkPathPriv::IsSimpleRect(const SkPath& path, bool isSimpleFill, SkRect* rect, SkPathDirection* direction, unsigned* start) { if (path.getSegmentMasks() != SkPath::kLine_SegmentMask) { return false; } SkPoint rectPts[5]; int rectPtCnt = 0; bool needsClose = !isSimpleFill; for (auto [v, verbPts, w] : SkPathPriv::Iterate(path)) { switch (v) { case SkPathVerb::kMove: if (0 != rectPtCnt) { return false; } rectPts[0] = verbPts[0]; ++rectPtCnt; break; case SkPathVerb::kLine: if (5 == rectPtCnt) { return false; } rectPts[rectPtCnt] = verbPts[1]; ++rectPtCnt; break; case SkPathVerb::kClose: if (4 == rectPtCnt) { rectPts[4] = rectPts[0]; rectPtCnt = 5; } needsClose = false; break; case SkPathVerb::kQuad: case SkPathVerb::kConic: case SkPathVerb::kCubic: return false; } } if (needsClose) { return false; } if (rectPtCnt < 5) { return false; } if (rectPts[0] != rectPts[4]) { return false; } // Check for two cases of rectangles: pts 0 and 3 form a vertical edge or a horizontal edge ( // and pts 1 and 2 the opposite vertical or horizontal edge). bool vec03IsVertical; if (rectPts[0].fX == rectPts[3].fX && rectPts[1].fX == rectPts[2].fX && rectPts[0].fY == rectPts[1].fY && rectPts[3].fY == rectPts[2].fY) { // Make sure it has non-zero width and height if (rectPts[0].fX == rectPts[1].fX || rectPts[0].fY == rectPts[3].fY) { return false; } vec03IsVertical = true; } else if (rectPts[0].fY == rectPts[3].fY && rectPts[1].fY == rectPts[2].fY && rectPts[0].fX == rectPts[1].fX && rectPts[3].fX == rectPts[2].fX) { // Make sure it has non-zero width and height if (rectPts[0].fY == rectPts[1].fY || rectPts[0].fX == rectPts[3].fX) { return false; } vec03IsVertical = false; } else { return false; } // Set sortFlags so that it has the low bit set if pt index 0 is on right edge and second bit // set if it is on the bottom edge. unsigned sortFlags = ((rectPts[0].fX < rectPts[2].fX) ? 0b00 : 0b01) | ((rectPts[0].fY < rectPts[2].fY) ? 0b00 : 0b10); switch (sortFlags) { case 0b00: rect->setLTRB(rectPts[0].fX, rectPts[0].fY, rectPts[2].fX, rectPts[2].fY); *direction = vec03IsVertical ? SkPathDirection::kCW : SkPathDirection::kCCW; *start = 0; break; case 0b01: rect->setLTRB(rectPts[2].fX, rectPts[0].fY, rectPts[0].fX, rectPts[2].fY); *direction = vec03IsVertical ? SkPathDirection::kCCW : SkPathDirection::kCW; *start = 1; break; case 0b10: rect->setLTRB(rectPts[0].fX, rectPts[2].fY, rectPts[2].fX, rectPts[0].fY); *direction = vec03IsVertical ? SkPathDirection::kCCW : SkPathDirection::kCW; *start = 3; break; case 0b11: rect->setLTRB(rectPts[2].fX, rectPts[2].fY, rectPts[0].fX, rectPts[0].fY); *direction = vec03IsVertical ? SkPathDirection::kCW : SkPathDirection::kCCW; *start = 2; break; } return true; } bool SkPathPriv::DrawArcIsConvex(SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) { if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) { // This gets converted to an oval. return true; } if (useCenter) { // This is a pie wedge. It's convex if the angle is <= 180. return SkScalarAbs(sweepAngle) <= 180.f; } // When the angle exceeds 360 this wraps back on top of itself. Otherwise it is a circle clipped // to a secant, i.e. convex. return SkScalarAbs(sweepAngle) <= 360.f; } void SkPathPriv::CreateDrawArcPath(SkPath* path, const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool useCenter, bool isFillNoPathEffect) { SkASSERT(!oval.isEmpty()); SkASSERT(sweepAngle); #if defined(SK_BUILD_FOR_FUZZER) if (sweepAngle > 3600.0f || sweepAngle < -3600.0f) { return; } #endif path->reset(); path->setIsVolatile(true); path->setFillType(SkPathFillType::kWinding); if (isFillNoPathEffect && SkScalarAbs(sweepAngle) >= 360.f) { path->addOval(oval); SkASSERT(path->isConvex() && DrawArcIsConvex(sweepAngle, false, isFillNoPathEffect)); return; } if (useCenter) { path->moveTo(oval.centerX(), oval.centerY()); } auto firstDir = sweepAngle > 0 ? SkPathFirstDirection::kCW : SkPathFirstDirection::kCCW; bool convex = DrawArcIsConvex(sweepAngle, useCenter, isFillNoPathEffect); // Arc to mods at 360 and drawArc is not supposed to. bool forceMoveTo = !useCenter; while (sweepAngle <= -360.f) { path->arcTo(oval, startAngle, -180.f, forceMoveTo); startAngle -= 180.f; path->arcTo(oval, startAngle, -180.f, false); startAngle -= 180.f; forceMoveTo = false; sweepAngle += 360.f; } while (sweepAngle >= 360.f) { path->arcTo(oval, startAngle, 180.f, forceMoveTo); startAngle += 180.f; path->arcTo(oval, startAngle, 180.f, false); startAngle += 180.f; forceMoveTo = false; sweepAngle -= 360.f; } path->arcTo(oval, startAngle, sweepAngle, forceMoveTo); if (useCenter) { path->close(); } path->setConvexity(convex ? SkPathConvexity::kConvex : SkPathConvexity::kConcave); path->setFirstDirection(firstDir); } /////////////////////////////////////////////////////////////////////////////////////////////////// static int compute_quad_extremas(const SkPoint src[3], SkPoint extremas[3]) { SkScalar ts[2]; int n = SkFindQuadExtrema(src[0].fX, src[1].fX, src[2].fX, ts); n += SkFindQuadExtrema(src[0].fY, src[1].fY, src[2].fY, &ts[n]); SkASSERT(n >= 0 && n <= 2); for (int i = 0; i < n; ++i) { extremas[i] = SkEvalQuadAt(src, ts[i]); } extremas[n] = src[2]; return n + 1; } static int compute_conic_extremas(const SkPoint src[3], SkScalar w, SkPoint extremas[3]) { SkConic conic(src[0], src[1], src[2], w); SkScalar ts[2]; int n = conic.findXExtrema(ts); n += conic.findYExtrema(&ts[n]); SkASSERT(n >= 0 && n <= 2); for (int i = 0; i < n; ++i) { extremas[i] = conic.evalAt(ts[i]); } extremas[n] = src[2]; return n + 1; } static int compute_cubic_extremas(const SkPoint src[4], SkPoint extremas[5]) { SkScalar ts[4]; int n = SkFindCubicExtrema(src[0].fX, src[1].fX, src[2].fX, src[3].fX, ts); n += SkFindCubicExtrema(src[0].fY, src[1].fY, src[2].fY, src[3].fY, &ts[n]); SkASSERT(n >= 0 && n <= 4); for (int i = 0; i < n; ++i) { SkEvalCubicAt(src, ts[i], &extremas[i], nullptr, nullptr); } extremas[n] = src[3]; return n + 1; } SkRect SkPath::computeTightBounds() const { if (0 == this->countVerbs()) { return SkRect::MakeEmpty(); } if (this->getSegmentMasks() == SkPath::kLine_SegmentMask) { return this->getBounds(); } SkPoint extremas[5]; // big enough to hold worst-case curve type (cubic) extremas + 1 // initial with the first MoveTo, so we don't have to check inside the switch skvx::float2 min, max; min = max = from_point(this->getPoint(0)); for (auto [verb, pts, w] : SkPathPriv::Iterate(*this)) { int count = 0; switch (verb) { case SkPathVerb::kMove: extremas[0] = pts[0]; count = 1; break; case SkPathVerb::kLine: extremas[0] = pts[1]; count = 1; break; case SkPathVerb::kQuad: count = compute_quad_extremas(pts, extremas); break; case SkPathVerb::kConic: count = compute_conic_extremas(pts, *w, extremas); break; case SkPathVerb::kCubic: count = compute_cubic_extremas(pts, extremas); break; case SkPathVerb::kClose: break; } for (int i = 0; i < count; ++i) { skvx::float2 tmp = from_point(extremas[i]); min = skvx::min(min, tmp); max = skvx::max(max, tmp); } } SkRect bounds; min.store((SkPoint*)&bounds.fLeft); max.store((SkPoint*)&bounds.fRight); return bounds; } bool SkPath::IsLineDegenerate(const SkPoint& p1, const SkPoint& p2, bool exact) { return exact ? p1 == p2 : SkPointPriv::EqualsWithinTolerance(p1, p2); } bool SkPath::IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3, bool exact) { return exact ? p1 == p2 && p2 == p3 : SkPointPriv::EqualsWithinTolerance(p1, p2) && SkPointPriv::EqualsWithinTolerance(p2, p3); } bool SkPath::IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2, const SkPoint& p3, const SkPoint& p4, bool exact) { return exact ? p1 == p2 && p2 == p3 && p3 == p4 : SkPointPriv::EqualsWithinTolerance(p1, p2) && SkPointPriv::EqualsWithinTolerance(p2, p3) && SkPointPriv::EqualsWithinTolerance(p3, p4); } ////////////////////////////////////////////////////////////////////////////////////////////////// SkPathVerbAnalysis sk_path_analyze_verbs(const uint8_t vbs[], int verbCount) { SkPathVerbAnalysis info = {false, 0, 0, 0}; bool needMove = true; bool invalid = false; if (verbCount >= (INT_MAX / 3)) { // A path with an extremely high number of quad, conic or cubic verbs could cause // `info.points` to overflow. To prevent against this, we reject extremely large paths. This // check is conservative and assumes the worst case (in particular, it assumes that every // verb consumes 3 points, which would only happen for a path composed entirely of cubics). // This limits us to 700 million verbs, which is large enough for any reasonable use case. invalid = true; } else { for (int i = 0; i < verbCount; ++i) { switch ((SkPathVerb)vbs[i]) { case SkPathVerb::kMove: needMove = false; info.points += 1; break; case SkPathVerb::kLine: invalid |= needMove; info.segmentMask |= kLine_SkPathSegmentMask; info.points += 1; break; case SkPathVerb::kQuad: invalid |= needMove; info.segmentMask |= kQuad_SkPathSegmentMask; info.points += 2; break; case SkPathVerb::kConic: invalid |= needMove; info.segmentMask |= kConic_SkPathSegmentMask; info.points += 2; info.weights += 1; break; case SkPathVerb::kCubic: invalid |= needMove; info.segmentMask |= kCubic_SkPathSegmentMask; info.points += 3; break; case SkPathVerb::kClose: invalid |= needMove; needMove = true; break; default: invalid = true; break; } } } info.valid = !invalid; return info; } SkPath SkPath::Make(const SkPoint pts[], int pointCount, const uint8_t vbs[], int verbCount, const SkScalar ws[], int wCount, SkPathFillType ft, bool isVolatile) { if (verbCount <= 0) { return SkPath(); } const auto info = sk_path_analyze_verbs(vbs, verbCount); if (!info.valid || info.points > pointCount || info.weights > wCount) { SkDEBUGFAIL("invalid verbs and number of points/weights"); return SkPath(); } return MakeInternal(info, pts, vbs, verbCount, ws, ft, isVolatile); } SkPath SkPath::Rect(const SkRect& r, SkPathDirection dir, unsigned startIndex) { return SkPathBuilder().addRect(r, dir, startIndex).detach(); } SkPath SkPath::Oval(const SkRect& r, SkPathDirection dir) { return SkPathBuilder().addOval(r, dir).detach(); } SkPath SkPath::Oval(const SkRect& r, SkPathDirection dir, unsigned startIndex) { return SkPathBuilder().addOval(r, dir, startIndex).detach(); } SkPath SkPath::Circle(SkScalar x, SkScalar y, SkScalar r, SkPathDirection dir) { return SkPathBuilder().addCircle(x, y, r, dir).detach(); } SkPath SkPath::RRect(const SkRRect& rr, SkPathDirection dir) { return SkPathBuilder().addRRect(rr, dir).detach(); } SkPath SkPath::RRect(const SkRRect& rr, SkPathDirection dir, unsigned startIndex) { return SkPathBuilder().addRRect(rr, dir, startIndex).detach(); } SkPath SkPath::RRect(const SkRect& r, SkScalar rx, SkScalar ry, SkPathDirection dir) { return SkPathBuilder().addRRect(SkRRect::MakeRectXY(r, rx, ry), dir).detach(); } SkPath SkPath::Polygon(const SkPoint pts[], int count, bool isClosed, SkPathFillType ft, bool isVolatile) { return SkPathBuilder().addPolygon(pts, count, isClosed) .setFillType(ft) .setIsVolatile(isVolatile) .detach(); } SkPath SkPath::MakeInternal(const SkPathVerbAnalysis& analysis, const SkPoint points[], const uint8_t verbs[], int verbCount, const SkScalar conics[], SkPathFillType fillType, bool isVolatile) { return SkPath(sk_sp(new SkPathRef( SkPathRef::PointsArray(points, analysis.points), SkPathRef::VerbsArray(verbs, verbCount), SkPathRef::ConicWeightsArray(conics, analysis.weights), analysis.segmentMask)), fillType, isVolatile, SkPathConvexity::kUnknown, SkPathFirstDirection::kUnknown); } ////////////////////////////////////////////////////////////////////////////////////////////////// bool SkPathPriv::IsRectContour(const SkPath& path, bool allowPartial, int* currVerb, const SkPoint** ptsPtr, bool* isClosed, SkPathDirection* direction, SkRect* rect) { int corners = 0; SkPoint closeXY; // used to determine if final line falls on a diagonal SkPoint lineStart; // used to construct line from previous point const SkPoint* firstPt = nullptr; // first point in the rect (last of first moves) const SkPoint* lastPt = nullptr; // last point in the rect (last of lines or first if closed) SkPoint firstCorner; SkPoint thirdCorner; const SkPoint* pts = *ptsPtr; const SkPoint* savePts = nullptr; // used to allow caller to iterate through a pair of rects lineStart.set(0, 0); signed char directions[] = {-1, -1, -1, -1, -1}; // -1 to 3; -1 is uninitialized bool closedOrMoved = false; bool autoClose = false; bool insertClose = false; int verbCnt = path.fPathRef->countVerbs(); while (*currVerb < verbCnt && (!allowPartial || !autoClose)) { uint8_t verb = insertClose ? (uint8_t) SkPath::kClose_Verb : path.fPathRef->atVerb(*currVerb); switch (verb) { case SkPath::kClose_Verb: savePts = pts; autoClose = true; insertClose = false; [[fallthrough]]; case SkPath::kLine_Verb: { if (SkPath::kClose_Verb != verb) { lastPt = pts; } SkPoint lineEnd = SkPath::kClose_Verb == verb ? *firstPt : *pts++; SkVector lineDelta = lineEnd - lineStart; if (lineDelta.fX && lineDelta.fY) { return false; // diagonal } if (!lineDelta.isFinite()) { return false; // path contains infinity or NaN } if (lineStart == lineEnd) { break; // single point on side OK } int nextDirection = rect_make_dir(lineDelta.fX, lineDelta.fY); // 0 to 3 if (0 == corners) { directions[0] = nextDirection; corners = 1; closedOrMoved = false; lineStart = lineEnd; break; } if (closedOrMoved) { return false; // closed followed by a line } if (autoClose && nextDirection == directions[0]) { break; // colinear with first } closedOrMoved = autoClose; if (directions[corners - 1] == nextDirection) { if (3 == corners && SkPath::kLine_Verb == verb) { thirdCorner = lineEnd; } lineStart = lineEnd; break; // colinear segment } directions[corners++] = nextDirection; // opposite lines must point in opposite directions; xoring them should equal 2 switch (corners) { case 2: firstCorner = lineStart; break; case 3: if ((directions[0] ^ directions[2]) != 2) { return false; } thirdCorner = lineEnd; break; case 4: if ((directions[1] ^ directions[3]) != 2) { return false; } break; default: return false; // too many direction changes } lineStart = lineEnd; break; } case SkPath::kQuad_Verb: case SkPath::kConic_Verb: case SkPath::kCubic_Verb: return false; // quadratic, cubic not allowed case SkPath::kMove_Verb: if (allowPartial && !autoClose && directions[0] >= 0) { insertClose = true; *currVerb -= 1; // try move again afterwards goto addMissingClose; } if (!corners) { firstPt = pts; } else { closeXY = *firstPt - *lastPt; if (closeXY.fX && closeXY.fY) { return false; // we're diagonal, abort } } lineStart = *pts++; closedOrMoved = true; break; default: SkDEBUGFAIL("unexpected verb"); break; } *currVerb += 1; addMissingClose: ; } // Success if 4 corners and first point equals last if (corners < 3 || corners > 4) { return false; } if (savePts) { *ptsPtr = savePts; } // check if close generates diagonal closeXY = *firstPt - *lastPt; if (closeXY.fX && closeXY.fY) { return false; } if (rect) { rect->set(firstCorner, thirdCorner); } if (isClosed) { *isClosed = autoClose; } if (direction) { *direction = directions[0] == ((directions[1] + 1) & 3) ? SkPathDirection::kCW : SkPathDirection::kCCW; } return true; } bool SkPathPriv::IsNestedFillRects(const SkPath& path, SkRect rects[2], SkPathDirection dirs[2]) { SkDEBUGCODE(path.validate();) int currVerb = 0; const SkPoint* pts = path.fPathRef->points(); SkPathDirection testDirs[2]; SkRect testRects[2]; if (!IsRectContour(path, true, &currVerb, &pts, nullptr, &testDirs[0], &testRects[0])) { return false; } if (IsRectContour(path, false, &currVerb, &pts, nullptr, &testDirs[1], &testRects[1])) { if (testRects[0].contains(testRects[1])) { if (rects) { rects[0] = testRects[0]; rects[1] = testRects[1]; } if (dirs) { dirs[0] = testDirs[0]; dirs[1] = testDirs[1]; } return true; } if (testRects[1].contains(testRects[0])) { if (rects) { rects[0] = testRects[1]; rects[1] = testRects[0]; } if (dirs) { dirs[0] = testDirs[1]; dirs[1] = testDirs[0]; } return true; } } return false; } /////////////////////////////////////////////////////////////////////////////////////////////////// struct SkHalfPlane { SkScalar fA, fB, fC; SkScalar eval(SkScalar x, SkScalar y) const { return fA * x + fB * y + fC; } SkScalar operator()(SkScalar x, SkScalar y) const { return this->eval(x, y); } bool normalize() { double a = fA; double b = fB; double c = fC; double dmag = sqrt(a * a + b * b); // length of initial plane normal is zero if (dmag == 0) { fA = fB = 0; fC = SK_Scalar1; return true; } double dscale = sk_ieee_double_divide(1.0, dmag); a *= dscale; b *= dscale; c *= dscale; // check if we're not finite, or normal is zero-length if (!sk_float_isfinite(a) || !sk_float_isfinite(b) || !sk_float_isfinite(c) || (a == 0 && b == 0)) { fA = fB = 0; fC = SK_Scalar1; return false; } fA = a; fB = b; fC = c; return true; } enum Result { kAllNegative, kAllPositive, kMixed }; Result test(const SkRect& bounds) const { // check whether the diagonal aligned with the normal crosses the plane SkPoint diagMin, diagMax; if (fA >= 0) { diagMin.fX = bounds.fLeft; diagMax.fX = bounds.fRight; } else { diagMin.fX = bounds.fRight; diagMax.fX = bounds.fLeft; } if (fB >= 0) { diagMin.fY = bounds.fTop; diagMax.fY = bounds.fBottom; } else { diagMin.fY = bounds.fBottom; diagMax.fY = bounds.fTop; } SkScalar test = this->eval(diagMin.fX, diagMin.fY); SkScalar sign = test*this->eval(diagMax.fX, diagMax.fY); if (sign > 0) { // the path is either all on one side of the half-plane or the other if (test < 0) { return kAllNegative; } else { return kAllPositive; } } return kMixed; } }; // assumes plane is pre-normalized // If we fail in our calculations, we return the empty path static SkPath clip(const SkPath& path, const SkHalfPlane& plane) { SkMatrix mx, inv; SkPoint p0 = { -plane.fA*plane.fC, -plane.fB*plane.fC }; mx.setAll( plane.fB, plane.fA, p0.fX, -plane.fA, plane.fB, p0.fY, 0, 0, 1); if (!mx.invert(&inv)) { return SkPath(); } SkPath rotated; path.transform(inv, &rotated); if (!rotated.isFinite()) { return SkPath(); } SkScalar big = SK_ScalarMax; SkRect clip = {-big, 0, big, big }; struct Rec { SkPathBuilder fResult; SkPoint fPrev = {0,0}; } rec; SkEdgeClipper::ClipPath(rotated, clip, false, [](SkEdgeClipper* clipper, bool newCtr, void* ctx) { Rec* rec = (Rec*)ctx; bool addLineTo = false; SkPoint pts[4]; SkPath::Verb verb; while ((verb = clipper->next(pts)) != SkPath::kDone_Verb) { if (newCtr) { rec->fResult.moveTo(pts[0]); rec->fPrev = pts[0]; newCtr = false; } if (addLineTo || pts[0] != rec->fPrev) { rec->fResult.lineTo(pts[0]); } switch (verb) { case SkPath::kLine_Verb: rec->fResult.lineTo(pts[1]); rec->fPrev = pts[1]; break; case SkPath::kQuad_Verb: rec->fResult.quadTo(pts[1], pts[2]); rec->fPrev = pts[2]; break; case SkPath::kCubic_Verb: rec->fResult.cubicTo(pts[1], pts[2], pts[3]); rec->fPrev = pts[3]; break; default: break; } addLineTo = true; } }, &rec); rec.fResult.setFillType(path.getFillType()); SkPath result = rec.fResult.detach().makeTransform(mx); if (!result.isFinite()) { result = SkPath(); } return result; } // true means we have written to clippedPath bool SkPathPriv::PerspectiveClip(const SkPath& path, const SkMatrix& matrix, SkPath* clippedPath) { if (!matrix.hasPerspective()) { return false; } SkHalfPlane plane { matrix[SkMatrix::kMPersp0], matrix[SkMatrix::kMPersp1], matrix[SkMatrix::kMPersp2] - kW0PlaneDistance }; if (plane.normalize()) { switch (plane.test(path.getBounds())) { case SkHalfPlane::kAllPositive: return false; case SkHalfPlane::kMixed: { *clippedPath = clip(path, plane); return true; } default: break; // handled outside of the switch } } // clipped out (or failed) *clippedPath = SkPath(); return true; } int SkPathPriv::GenIDChangeListenersCount(const SkPath& path) { return path.fPathRef->genIDChangeListenerCount(); } bool SkPathPriv::IsAxisAligned(const SkPath& path) { // Conservative (quick) test to see if all segments are axis-aligned. // Multiple contours might give a false-negative, but for speed, we ignore that // and just look at the raw points. const SkPoint* pts = path.fPathRef->points(); const int count = path.fPathRef->countPoints(); for (int i = 1; i < count; ++i) { if (pts[i-1].fX != pts[i].fX && pts[i-1].fY != pts[i].fY) { return false; } } return true; } ////////////////////////////////////////////////////////////////////////////////////////////////// SkPathEdgeIter::SkPathEdgeIter(const SkPath& path) { fMoveToPtr = fPts = path.fPathRef->points(); fVerbs = path.fPathRef->verbsBegin(); fVerbsStop = path.fPathRef->verbsEnd(); fConicWeights = path.fPathRef->conicWeights(); if (fConicWeights) { fConicWeights -= 1; // begin one behind } fNeedsCloseLine = false; fNextIsNewContour = false; SkDEBUGCODE(fIsConic = false;) } SkPathBuilder::SkPathBuilder() { this->reset(); } SkPathBuilder::SkPathBuilder(SkPathFillType ft) { this->reset(); fFillType = ft; } SkPathBuilder::SkPathBuilder(const SkPath& src) { *this = src; } SkPathBuilder::~SkPathBuilder() { } SkPathBuilder& SkPathBuilder::reset() { fPts.clear(); fVerbs.clear(); fConicWeights.clear(); fFillType = SkPathFillType::kWinding; fIsVolatile = false; // these are internal state fSegmentMask = 0; fLastMovePoint = {0, 0}; fLastMoveIndex = -1; // illegal fNeedsMoveVerb = true; return *this; } SkPathBuilder& SkPathBuilder::operator=(const SkPath& src) { this->reset().setFillType(src.getFillType()); for (auto [verb, pts, w] : SkPathPriv::Iterate(src)) { switch (verb) { case SkPathVerb::kMove: this->moveTo(pts[0]); break; case SkPathVerb::kLine: this->lineTo(pts[1]); break; case SkPathVerb::kQuad: this->quadTo(pts[1], pts[2]); break; case SkPathVerb::kConic: this->conicTo(pts[1], pts[2], w[0]); break; case SkPathVerb::kCubic: this->cubicTo(pts[1], pts[2], pts[3]); break; case SkPathVerb::kClose: this->close(); break; } } return *this; } void SkPathBuilder::incReserve(int extraPtCount, int extraVbCount) { fPts.reserve_exact(Sk32_sat_add(fPts.size(), extraPtCount)); fVerbs.reserve_exact(Sk32_sat_add(fVerbs.size(), extraVbCount)); } SkRect SkPathBuilder::computeBounds() const { SkRect bounds; bounds.setBounds(fPts.begin(), fPts.size()); return bounds; } /* * Some old behavior in SkPath -- should we keep it? * * After each edit (i.e. adding a verb) this->setConvexityType(SkPathConvexity::kUnknown); this->setFirstDirection(SkPathPriv::kUnknown_FirstDirection); */ SkPathBuilder& SkPathBuilder::moveTo(SkPoint pt) { // only needed while SkPath is mutable fLastMoveIndex = SkToInt(fPts.size()); fPts.push_back(pt); fVerbs.push_back((uint8_t)SkPathVerb::kMove); fLastMovePoint = pt; fNeedsMoveVerb = false; return *this; } SkPathBuilder& SkPathBuilder::lineTo(SkPoint pt) { this->ensureMove(); fPts.push_back(pt); fVerbs.push_back((uint8_t)SkPathVerb::kLine); fSegmentMask |= kLine_SkPathSegmentMask; return *this; } SkPathBuilder& SkPathBuilder::quadTo(SkPoint pt1, SkPoint pt2) { this->ensureMove(); SkPoint* p = fPts.push_back_n(2); p[0] = pt1; p[1] = pt2; fVerbs.push_back((uint8_t)SkPathVerb::kQuad); fSegmentMask |= kQuad_SkPathSegmentMask; return *this; } SkPathBuilder& SkPathBuilder::conicTo(SkPoint pt1, SkPoint pt2, SkScalar w) { this->ensureMove(); SkPoint* p = fPts.push_back_n(2); p[0] = pt1; p[1] = pt2; fVerbs.push_back((uint8_t)SkPathVerb::kConic); fConicWeights.push_back(w); fSegmentMask |= kConic_SkPathSegmentMask; return *this; } SkPathBuilder& SkPathBuilder::cubicTo(SkPoint pt1, SkPoint pt2, SkPoint pt3) { this->ensureMove(); SkPoint* p = fPts.push_back_n(3); p[0] = pt1; p[1] = pt2; p[2] = pt3; fVerbs.push_back((uint8_t)SkPathVerb::kCubic); fSegmentMask |= kCubic_SkPathSegmentMask; return *this; } SkPathBuilder& SkPathBuilder::close() { if (!fVerbs.empty()) { this->ensureMove(); fVerbs.push_back((uint8_t)SkPathVerb::kClose); // fLastMovePoint stays where it is -- the previous moveTo fNeedsMoveVerb = true; } return *this; } /////////////////////////////////////////////////////////////////////////////////////////// SkPathBuilder& SkPathBuilder::rLineTo(SkPoint p1) { this->ensureMove(); return this->lineTo(fPts.back() + p1); } SkPathBuilder& SkPathBuilder::rQuadTo(SkPoint p1, SkPoint p2) { this->ensureMove(); SkPoint base = fPts.back(); return this->quadTo(base + p1, base + p2); } SkPathBuilder& SkPathBuilder::rConicTo(SkPoint p1, SkPoint p2, SkScalar w) { this->ensureMove(); SkPoint base = fPts.back(); return this->conicTo(base + p1, base + p2, w); } SkPathBuilder& SkPathBuilder::rCubicTo(SkPoint p1, SkPoint p2, SkPoint p3) { this->ensureMove(); SkPoint base = fPts.back(); return this->cubicTo(base + p1, base + p2, base + p3); } /////////////////////////////////////////////////////////////////////////////////////////// SkPath SkPathBuilder::make(sk_sp pr) const { auto convexity = SkPathConvexity::kUnknown; SkPathFirstDirection dir = SkPathFirstDirection::kUnknown; switch (fIsA) { case kIsA_Oval: pr->setIsOval( true, fIsACCW, fIsAStart); convexity = SkPathConvexity::kConvex; dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW; break; case kIsA_RRect: pr->setIsRRect(true, fIsACCW, fIsAStart); convexity = SkPathConvexity::kConvex; dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW; break; default: break; } // Wonder if we can combine convexity and dir internally... // unknown, convex_cw, convex_ccw, concave // Do we ever have direction w/o convexity, or viceversa (inside path)? // auto path = SkPath(std::move(pr), fFillType, fIsVolatile, convexity, dir); // This hopefully can go away in the future when Paths are immutable, // but if while they are still editable, we need to correctly set this. const uint8_t* start = path.fPathRef->verbsBegin(); const uint8_t* stop = path.fPathRef->verbsEnd(); if (start < stop) { SkASSERT(fLastMoveIndex >= 0); // peek at the last verb, to know if our last contour is closed const bool isClosed = (stop[-1] == (uint8_t)SkPathVerb::kClose); path.fLastMoveToIndex = isClosed ? ~fLastMoveIndex : fLastMoveIndex; } return path; } SkPath SkPathBuilder::snapshot() const { return this->make(sk_sp(new SkPathRef(fPts, fVerbs, fConicWeights, fSegmentMask))); } SkPath SkPathBuilder::detach() { auto path = this->make(sk_sp(new SkPathRef(std::move(fPts), std::move(fVerbs), std::move(fConicWeights), fSegmentMask))); this->reset(); return path; } /////////////////////////////////////////////////////////////////////////////////////////////////// static bool skPathBuilder_arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, SkPoint* pt) { if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) { // Chrome uses this path to move into and out of ovals. If not // treated as a special case the moves can distort the oval's // bounding box (and break the circle special case). pt->set(oval.fRight, oval.centerY()); return true; } else if (0 == oval.width() && 0 == oval.height()) { // Chrome will sometimes create 0 radius round rects. Having degenerate // quad segments in the path prevents the path from being recognized as // a rect. // TODO: optimizing the case where only one of width or height is zero // should also be considered. This case, however, doesn't seem to be // as common as the single point case. pt->set(oval.fRight, oval.fTop); return true; } return false; } // Return the unit vectors pointing at the start/stop points for the given start/sweep angles // static void skPathBuilder_angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle, SkVector* startV, SkVector* stopV, SkRotationDirection* dir) { SkScalar startRad = SkDegreesToRadians(startAngle), stopRad = SkDegreesToRadians(startAngle + sweepAngle); startV->fY = SkScalarSinSnapToZero(startRad); startV->fX = SkScalarCosSnapToZero(startRad); stopV->fY = SkScalarSinSnapToZero(stopRad); stopV->fX = SkScalarCosSnapToZero(stopRad); /* If the sweep angle is nearly (but less than) 360, then due to precision loss in radians-conversion and/or sin/cos, we may end up with coincident vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead of drawing a nearly complete circle (good). e.g. canvas.drawArc(0, 359.99, ...) -vs- canvas.drawArc(0, 359.9, ...) We try to detect this edge case, and tweak the stop vector */ if (*startV == *stopV) { SkScalar sw = SkScalarAbs(sweepAngle); if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) { // make a guess at a tiny angle (in radians) to tweak by SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle); // not sure how much will be enough, so we use a loop do { stopRad -= deltaRad; stopV->fY = SkScalarSinSnapToZero(stopRad); stopV->fX = SkScalarCosSnapToZero(stopRad); } while (*startV == *stopV); } } *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection; } /** * If this returns 0, then the caller should just line-to the singlePt, else it should * ignore singlePt and append the specified number of conics. */ static int skPathBuilder_build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop, SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc], SkPoint* singlePt) { SkMatrix matrix; matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height())); matrix.postTranslate(oval.centerX(), oval.centerY()); int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics); if (0 == count) { matrix.mapXY(stop.x(), stop.y(), singlePt); } return count; } static bool nearly_equal(const SkPoint& a, const SkPoint& b) { return SkScalarNearlyEqual(a.fX, b.fX) && SkScalarNearlyEqual(a.fY, b.fY); } SkPathBuilder& SkPathBuilder::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle, bool forceMoveTo) { if (oval.width() < 0 || oval.height() < 0) { return *this; } if (fVerbs.empty()) { forceMoveTo = true; } SkPoint lonePt; if (skPathBuilder_arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) { return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt); } SkVector startV, stopV; SkRotationDirection dir; skPathBuilder_angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir); SkPoint singlePt; // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous // arcs from the same oval. auto addPt = [forceMoveTo, this](const SkPoint& pt) { if (forceMoveTo) { this->moveTo(pt); } else if (!nearly_equal(fPts.back(), pt)) { this->lineTo(pt); } }; // At this point, we know that the arc is not a lone point, but startV == stopV // indicates that the sweepAngle is too small such that skPathBuilder_angles_to_unit_vectors // cannot handle it. if (startV == stopV) { SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle); SkScalar radiusX = oval.width() / 2; SkScalar radiusY = oval.height() / 2; // We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle // is very small and radius is huge, the expected behavior here is to draw a line. But // calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot. singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle), oval.centerY() + radiusY * SkScalarSin(endAngle)); addPt(singlePt); return *this; } SkConic conics[SkConic::kMaxConicsForArc]; int count = skPathBuilder_build_arc_conics(oval, startV, stopV, dir, conics, &singlePt); if (count) { this->incReserve(count * 2 + 1); const SkPoint& pt = conics[0].fPts[0]; addPt(pt); for (int i = 0; i < count; ++i) { this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW); } } else { addPt(singlePt); } return *this; } SkPathBuilder& SkPathBuilder::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) { if (oval.isEmpty() || 0 == sweepAngle) { return *this; } const SkScalar kFullCircleAngle = SkIntToScalar(360); if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) { // We can treat the arc as an oval if it begins at one of our legal starting positions. // See SkPath::addOval() docs. SkScalar startOver90 = startAngle / 90.f; SkScalar startOver90I = SkScalarRoundToScalar(startOver90); SkScalar error = startOver90 - startOver90I; if (SkScalarNearlyEqual(error, 0)) { // Index 1 is at startAngle == 0. SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f); startIndex = startIndex < 0 ? startIndex + 4.f : startIndex; return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW, (unsigned) startIndex); } } return this->arcTo(oval, startAngle, sweepAngle, true); } SkPathBuilder& SkPathBuilder::arcTo(SkPoint p1, SkPoint p2, SkScalar radius) { this->ensureMove(); if (radius == 0) { return this->lineTo(p1); } // need to know our prev pt so we can construct tangent vectors SkPoint start = fPts.back(); // need double precision for these calcs. skvx::double2 befored = normalize(skvx::double2{p1.fX - start.fX, p1.fY - start.fY}); skvx::double2 afterd = normalize(skvx::double2{p2.fX - p1.fX, p2.fY - p1.fY}); double cosh = dot(befored, afterd); double sinh = cross(befored, afterd); // If the previous point equals the first point, befored will be denormalized. // If the two points equal, afterd will be denormalized. // If the second point equals the first point, sinh will be zero. // In all these cases, we cannot construct an arc, so we construct a line to the first point. if (!isfinite(befored) || !isfinite(afterd) || SkScalarNearlyZero(SkDoubleToScalar(sinh))) { return this->lineTo(p1); } // safe to convert back to floats now SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh)); SkScalar xx = p1.fX - dist * befored[0]; SkScalar yy = p1.fY - dist * befored[1]; SkVector after = SkVector::Make(afterd[0], afterd[1]); after.setLength(dist); this->lineTo(xx, yy); SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5)); return this->conicTo(p1, p1 + after, weight); } // This converts the SVG arc to conics. // Partly adapted from Niko's code in kdelibs/kdecore/svgicons. // Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic() // See also SVG implementation notes: // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter // Note that arcSweep bool value is flipped from the original implementation. SkPathBuilder& SkPathBuilder::arcTo(SkPoint rad, SkScalar angle, SkPathBuilder::ArcSize arcLarge, SkPathDirection arcSweep, SkPoint endPt) { this->ensureMove(); SkPoint srcPts[2] = { fPts.back(), endPt }; // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto") // joining the endpoints. // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters if (!rad.fX || !rad.fY) { return this->lineTo(endPt); } // If the current point and target point for the arc are identical, it should be treated as a // zero length path. This ensures continuity in animations. if (srcPts[0] == srcPts[1]) { return this->lineTo(endPt); } SkScalar rx = SkScalarAbs(rad.fX); SkScalar ry = SkScalarAbs(rad.fY); SkVector midPointDistance = srcPts[0] - srcPts[1]; midPointDistance *= 0.5f; SkMatrix pointTransform; pointTransform.setRotate(-angle); SkPoint transformedMidPoint; pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1); SkScalar squareRx = rx * rx; SkScalar squareRy = ry * ry; SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX; SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY; // Check if the radii are big enough to draw the arc, scale radii if not. // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii SkScalar radiiScale = squareX / squareRx + squareY / squareRy; if (radiiScale > 1) { radiiScale = SkScalarSqrt(radiiScale); rx *= radiiScale; ry *= radiiScale; } pointTransform.setScale(1 / rx, 1 / ry); pointTransform.preRotate(-angle); SkPoint unitPts[2]; pointTransform.mapPoints(unitPts, srcPts, (int) std::size(unitPts)); SkVector delta = unitPts[1] - unitPts[0]; SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY; SkScalar scaleFactorSquared = std::max(1 / d - 0.25f, 0.f); SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared); if ((arcSweep == SkPathDirection::kCCW) != SkToBool(arcLarge)) { // flipped from the original implementation scaleFactor = -scaleFactor; } delta.scale(scaleFactor); SkPoint centerPoint = unitPts[0] + unitPts[1]; centerPoint *= 0.5f; centerPoint.offset(-delta.fY, delta.fX); unitPts[0] -= centerPoint; unitPts[1] -= centerPoint; SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX); SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX); SkScalar thetaArc = theta2 - theta1; if (thetaArc < 0 && (arcSweep == SkPathDirection::kCW)) { // arcSweep flipped from the original implementation thetaArc += SK_ScalarPI * 2; } else if (thetaArc > 0 && (arcSweep != SkPathDirection::kCW)) { // arcSweep flipped from the original implementation thetaArc -= SK_ScalarPI * 2; } // Very tiny angles cause our subsequent math to go wonky (skbug.com/9272) // so we do a quick check here. The precise tolerance amount is just made up. // PI/million happens to fix the bug in 9272, but a larger value is probably // ok too. if (SkScalarAbs(thetaArc) < (SK_ScalarPI / (1000 * 1000))) { return this->lineTo(endPt); } pointTransform.setRotate(angle); pointTransform.preScale(rx, ry); // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3))); SkScalar thetaWidth = thetaArc / segments; SkScalar t = SkScalarTan(0.5f * thetaWidth); if (!SkScalarIsFinite(t)) { return *this; } SkScalar startTheta = theta1; SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf); auto scalar_is_integer = [](SkScalar scalar) -> bool { return scalar == SkScalarFloorToScalar(scalar); }; bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) && scalar_is_integer(rx) && scalar_is_integer(ry) && scalar_is_integer(endPt.fX) && scalar_is_integer(endPt.fY); for (int i = 0; i < segments; ++i) { SkScalar endTheta = startTheta + thetaWidth, sinEndTheta = SkScalarSinSnapToZero(endTheta), cosEndTheta = SkScalarCosSnapToZero(endTheta); unitPts[1].set(cosEndTheta, sinEndTheta); unitPts[1] += centerPoint; unitPts[0] = unitPts[1]; unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta); SkPoint mapped[2]; pointTransform.mapPoints(mapped, unitPts, (int) std::size(unitPts)); /* Computing the arc width introduces rounding errors that cause arcs to start outside their marks. A round rect may lose convexity as a result. If the input values are on integers, place the conic on integers as well. */ if (expectIntegers) { for (SkPoint& point : mapped) { point.fX = SkScalarRoundToScalar(point.fX); point.fY = SkScalarRoundToScalar(point.fY); } } this->conicTo(mapped[0], mapped[1], w); startTheta = endTheta; } // The final point should match the input point (by definition); replace it to // ensure that rounding errors in the above math don't cause any problems. fPts.back() = endPt; return *this; } /////////////////////////////////////////////////////////////////////////////////////////// namespace { template class PointIterator { public: PointIterator(SkPathDirection dir, unsigned startIndex) : fCurrent(startIndex % N) , fAdvance(dir == SkPathDirection::kCW ? 1 : N - 1) {} const SkPoint& current() const { SkASSERT(fCurrent < N); return fPts[fCurrent]; } const SkPoint& next() { fCurrent = (fCurrent + fAdvance) % N; return this->current(); } protected: SkPoint fPts[N]; private: unsigned fCurrent; unsigned fAdvance; }; class RectPointIterator : public PointIterator<4> { public: RectPointIterator(const SkRect& rect, SkPathDirection dir, unsigned startIndex) : PointIterator(dir, startIndex) { fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop); fPts[1] = SkPoint::Make(rect.fRight, rect.fTop); fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom); fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom); } }; class OvalPointIterator : public PointIterator<4> { public: OvalPointIterator(const SkRect& oval, SkPathDirection dir, unsigned startIndex) : PointIterator(dir, startIndex) { const SkScalar cx = oval.centerX(); const SkScalar cy = oval.centerY(); fPts[0] = SkPoint::Make(cx, oval.fTop); fPts[1] = SkPoint::Make(oval.fRight, cy); fPts[2] = SkPoint::Make(cx, oval.fBottom); fPts[3] = SkPoint::Make(oval.fLeft, cy); } }; class RRectPointIterator : public PointIterator<8> { public: RRectPointIterator(const SkRRect& rrect, SkPathDirection dir, unsigned startIndex) : PointIterator(dir, startIndex) { const SkRect& bounds = rrect.getBounds(); const SkScalar L = bounds.fLeft; const SkScalar T = bounds.fTop; const SkScalar R = bounds.fRight; const SkScalar B = bounds.fBottom; fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T); fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T); fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY); fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY); fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B); fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B); fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY); fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY); } }; } // anonymous namespace SkPathBuilder& SkPathBuilder::addRect(const SkRect& rect, SkPathDirection dir, unsigned index) { const int kPts = 4; // moveTo + 3 lines const int kVerbs = 5; // moveTo + 3 lines + close this->incReserve(kPts, kVerbs); RectPointIterator iter(rect, dir, index); this->moveTo(iter.current()); this->lineTo(iter.next()); this->lineTo(iter.next()); this->lineTo(iter.next()); return this->close(); } SkPathBuilder& SkPathBuilder::addOval(const SkRect& oval, SkPathDirection dir, unsigned index) { const IsA prevIsA = fIsA; const int kPts = 9; // moveTo + 4 conics(2 pts each) const int kVerbs = 6; // moveTo + 4 conics + close this->incReserve(kPts, kVerbs); OvalPointIterator ovalIter(oval, dir, index); RectPointIterator rectIter(oval, dir, index + (dir == SkPathDirection::kCW ? 0 : 1)); // The corner iterator pts are tracking "behind" the oval/radii pts. this->moveTo(ovalIter.current()); for (unsigned i = 0; i < 4; ++i) { this->conicTo(rectIter.next(), ovalIter.next(), SK_ScalarRoot2Over2); } this->close(); if (prevIsA == kIsA_JustMoves) { fIsA = kIsA_Oval; fIsACCW = (dir == SkPathDirection::kCCW); fIsAStart = index % 4; } return *this; } SkPathBuilder& SkPathBuilder::addRRect(const SkRRect& rrect, SkPathDirection dir, unsigned index) { const IsA prevIsA = fIsA; const SkRect& bounds = rrect.getBounds(); if (rrect.isRect() || rrect.isEmpty()) { // degenerate(rect) => radii points are collapsing this->addRect(bounds, dir, (index + 1) / 2); } else if (rrect.isOval()) { // degenerate(oval) => line points are collapsing this->addOval(bounds, dir, index / 2); } else { // we start with a conic on odd indices when moving CW vs. even indices when moving CCW const bool startsWithConic = ((index & 1) == (dir == SkPathDirection::kCW)); const SkScalar weight = SK_ScalarRoot2Over2; const int kVerbs = startsWithConic ? 9 // moveTo + 4x conicTo + 3x lineTo + close : 10; // moveTo + 4x lineTo + 4x conicTo + close this->incReserve(kVerbs); RRectPointIterator rrectIter(rrect, dir, index); // Corner iterator indices follow the collapsed radii model, // adjusted such that the start pt is "behind" the radii start pt. const unsigned rectStartIndex = index / 2 + (dir == SkPathDirection::kCW ? 0 : 1); RectPointIterator rectIter(bounds, dir, rectStartIndex); this->moveTo(rrectIter.current()); if (startsWithConic) { for (unsigned i = 0; i < 3; ++i) { this->conicTo(rectIter.next(), rrectIter.next(), weight); this->lineTo(rrectIter.next()); } this->conicTo(rectIter.next(), rrectIter.next(), weight); // final lineTo handled by close(). } else { for (unsigned i = 0; i < 4; ++i) { this->lineTo(rrectIter.next()); this->conicTo(rectIter.next(), rrectIter.next(), weight); } } this->close(); } if (prevIsA == kIsA_JustMoves) { fIsA = kIsA_RRect; fIsACCW = (dir == SkPathDirection::kCCW); fIsAStart = index % 8; } return *this; } SkPathBuilder& SkPathBuilder::addCircle(SkScalar x, SkScalar y, SkScalar r, SkPathDirection dir) { if (r >= 0) { this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir); } return *this; } SkPathBuilder& SkPathBuilder::addPolygon(const SkPoint pts[], int count, bool isClosed) { if (count <= 0) { return *this; } this->moveTo(pts[0]); this->polylineTo(&pts[1], count - 1); if (isClosed) { this->close(); } return *this; } SkPathBuilder& SkPathBuilder::polylineTo(const SkPoint pts[], int count) { if (count > 0) { this->ensureMove(); this->incReserve(count, count); memcpy(fPts.push_back_n(count), pts, count * sizeof(SkPoint)); memset(fVerbs.push_back_n(count), (uint8_t)SkPathVerb::kLine, count); fSegmentMask |= kLine_SkPathSegmentMask; } return *this; } ////////////////////////////////////////////////////////////////////////////////////////////////// SkPathBuilder& SkPathBuilder::offset(SkScalar dx, SkScalar dy) { for (auto& p : fPts) { p += {dx, dy}; } return *this; } SkPathBuilder& SkPathBuilder::addPath(const SkPath& src) { SkPath::RawIter iter(src); SkPoint pts[4]; SkPath::Verb verb; while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: this->moveTo (pts[0]); break; case SkPath::kLine_Verb: this->lineTo (pts[1]); break; case SkPath::kQuad_Verb: this->quadTo (pts[1], pts[2]); break; case SkPath::kCubic_Verb: this->cubicTo(pts[1], pts[2], pts[3]); break; case SkPath::kConic_Verb: this->conicTo(pts[1], pts[2], iter.conicWeight()); break; case SkPath::kClose_Verb: this->close(); break; case SkPath::kDone_Verb: SkUNREACHABLE; } } return *this; } SkPathBuilder& SkPathBuilder::privateReverseAddPath(const SkPath& src) { const uint8_t* verbsBegin = src.fPathRef->verbsBegin(); const uint8_t* verbs = src.fPathRef->verbsEnd(); const SkPoint* pts = src.fPathRef->pointsEnd(); const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd(); bool needMove = true; bool needClose = false; while (verbs > verbsBegin) { uint8_t v = *--verbs; int n = SkPathPriv::PtsInVerb(v); if (needMove) { --pts; this->moveTo(pts->fX, pts->fY); needMove = false; } pts -= n; switch ((SkPathVerb)v) { case SkPathVerb::kMove: if (needClose) { this->close(); needClose = false; } needMove = true; pts += 1; // so we see the point in "if (needMove)" above break; case SkPathVerb::kLine: this->lineTo(pts[0]); break; case SkPathVerb::kQuad: this->quadTo(pts[1], pts[0]); break; case SkPathVerb::kConic: this->conicTo(pts[1], pts[0], *--conicWeights); break; case SkPathVerb::kCubic: this->cubicTo(pts[2], pts[1], pts[0]); break; case SkPathVerb::kClose: needClose = true; break; default: SkDEBUGFAIL("unexpected verb"); } } return *this; } #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK static constexpr int kPathRefGenIDBitCnt = 30; // leave room for the fill type (skbug.com/1762) #else static constexpr int kPathRefGenIDBitCnt = 32; #endif ////////////////////////////////////////////////////////////////////////////// SkPathRef::Editor::Editor(sk_sp* pathRef, int incReserveVerbs, int incReservePoints) { SkASSERT(incReserveVerbs >= 0); SkASSERT(incReservePoints >= 0); if ((*pathRef)->unique()) { (*pathRef)->incReserve(incReserveVerbs, incReservePoints); } else { SkPathRef* copy; // No need to copy if the existing ref is the empty ref (because it doesn't contain // anything). if (!(*pathRef)->isInitialEmptyPathRef()) { copy = new SkPathRef; copy->copy(**pathRef, incReserveVerbs, incReservePoints); } else { // Size previously empty paths to exactly fit the supplied hints. The assumpion is // the caller knows the exact size they want (as happens in chrome when deserializing // paths). copy = new SkPathRef(incReserveVerbs, incReservePoints); } pathRef->reset(copy); } fPathRef = pathRef->get(); fPathRef->callGenIDChangeListeners(); fPathRef->fGenerationID = 0; fPathRef->fBoundsIsDirty = true; SkDEBUGCODE(fPathRef->fEditorsAttached++;) } ////////////////////////////////////////////////////////////////////////////// size_t SkPathRef::approximateBytesUsed() const { return sizeof(SkPathRef) + fPoints .capacity() * sizeof(fPoints [0]) + fVerbs .capacity() * sizeof(fVerbs [0]) + fConicWeights.capacity() * sizeof(fConicWeights[0]); } SkPathRef::~SkPathRef() { // Deliberately don't validate() this path ref, otherwise there's no way // to read one that's not valid and then free its memory without asserting. SkDEBUGCODE(fGenerationID = 0xEEEEEEEE;) SkDEBUGCODE(fEditorsAttached.store(0x7777777);) } static SkPathRef* gEmpty = nullptr; SkPathRef* SkPathRef::CreateEmpty() { static SkOnce once; once([]{ gEmpty = new SkPathRef; gEmpty->computeBounds(); // Avoids races later to be the first to do this. }); return SkRef(gEmpty); } static void transform_dir_and_start(const SkMatrix& matrix, bool isRRect, bool* isCCW, unsigned* start) { int inStart = *start; int rm = 0; if (isRRect) { // Degenerate rrect indices to oval indices and remember the remainder. // Ovals have one index per side whereas rrects have two. rm = inStart & 0b1; inStart /= 2; } // Is the antidiagonal non-zero (otherwise the diagonal is zero) int antiDiag; // Is the non-zero value in the top row (either kMScaleX or kMSkewX) negative int topNeg; // Are the two non-zero diagonal or antidiagonal values the same sign. int sameSign; if (matrix.get(SkMatrix::kMScaleX) != 0) { antiDiag = 0b00; if (matrix.get(SkMatrix::kMScaleX) > 0) { topNeg = 0b00; sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b01 : 0b00; } else { topNeg = 0b10; sameSign = matrix.get(SkMatrix::kMScaleY) > 0 ? 0b00 : 0b01; } } else { antiDiag = 0b01; if (matrix.get(SkMatrix::kMSkewX) > 0) { topNeg = 0b00; sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b01 : 0b00; } else { topNeg = 0b10; sameSign = matrix.get(SkMatrix::kMSkewY) > 0 ? 0b00 : 0b01; } } if (sameSign != antiDiag) { // This is a rotation (and maybe scale). The direction is unchanged. // Trust me on the start computation (or draw yourself some pictures) *start = (inStart + 4 - (topNeg | antiDiag)) % 4; SkASSERT(*start < 4); if (isRRect) { *start = 2 * *start + rm; } } else { // This is a mirror (and maybe scale). The direction is reversed. *isCCW = !*isCCW; // Trust me on the start computation (or draw yourself some pictures) *start = (6 + (topNeg | antiDiag) - inStart) % 4; SkASSERT(*start < 4); if (isRRect) { *start = 2 * *start + (rm ? 0 : 1); } } } void SkPathRef::CreateTransformedCopy(sk_sp* dst, const SkPathRef& src, const SkMatrix& matrix) { SkDEBUGCODE(src.validate();) if (matrix.isIdentity()) { if (dst->get() != &src) { src.ref(); dst->reset(const_cast(&src)); SkDEBUGCODE((*dst)->validate();) } return; } sk_sp srcKeepAlive; if (!(*dst)->unique()) { // If dst and src are the same then we are about to drop our only ref on the common path // ref. Some other thread may have owned src when we checked unique() above but it may not // continue to do so. Add another ref so we continue to be an owner until we're done. if (dst->get() == &src) { srcKeepAlive.reset(SkRef(&src)); } dst->reset(new SkPathRef); } if (dst->get() != &src) { (*dst)->fVerbs = src.fVerbs; (*dst)->fConicWeights = src.fConicWeights; (*dst)->callGenIDChangeListeners(); (*dst)->fGenerationID = 0; // mark as dirty // don't copy, just allocate the points (*dst)->fPoints.resize(src.fPoints.size()); } matrix.mapPoints((*dst)->fPoints.begin(), src.fPoints.begin(), src.fPoints.size()); // Need to check this here in case (&src == dst) bool canXformBounds = !src.fBoundsIsDirty && matrix.rectStaysRect() && src.countPoints() > 1; /* * Here we optimize the bounds computation, by noting if the bounds are * already known, and if so, we just transform those as well and mark * them as "known", rather than force the transformed path to have to * recompute them. * * Special gotchas if the path is effectively empty (<= 1 point) or * if it is non-finite. In those cases bounds need to stay empty, * regardless of the matrix. */ if (canXformBounds) { (*dst)->fBoundsIsDirty = false; if (src.fIsFinite) { matrix.mapRect(&(*dst)->fBounds, src.fBounds); if (!((*dst)->fIsFinite = (*dst)->fBounds.isFinite())) { (*dst)->fBounds.setEmpty(); } } else { (*dst)->fIsFinite = false; (*dst)->fBounds.setEmpty(); } } else { (*dst)->fBoundsIsDirty = true; } (*dst)->fSegmentMask = src.fSegmentMask; // It's an oval only if it stays a rect. bool rectStaysRect = matrix.rectStaysRect(); (*dst)->fIsOval = src.fIsOval && rectStaysRect; (*dst)->fIsRRect = src.fIsRRect && rectStaysRect; if ((*dst)->fIsOval || (*dst)->fIsRRect) { unsigned start = src.fRRectOrOvalStartIdx; bool isCCW = SkToBool(src.fRRectOrOvalIsCCW); transform_dir_and_start(matrix, (*dst)->fIsRRect, &isCCW, &start); (*dst)->fRRectOrOvalIsCCW = isCCW; (*dst)->fRRectOrOvalStartIdx = start; } if (dst->get() == &src) { (*dst)->callGenIDChangeListeners(); (*dst)->fGenerationID = 0; } SkDEBUGCODE((*dst)->validate();) } void SkPathRef::Rewind(sk_sp* pathRef) { if ((*pathRef)->unique()) { SkDEBUGCODE((*pathRef)->validate();) (*pathRef)->callGenIDChangeListeners(); (*pathRef)->fBoundsIsDirty = true; // this also invalidates fIsFinite (*pathRef)->fGenerationID = 0; (*pathRef)->fPoints.clear(); (*pathRef)->fVerbs.clear(); (*pathRef)->fConicWeights.clear(); (*pathRef)->fSegmentMask = 0; (*pathRef)->fIsOval = false; (*pathRef)->fIsRRect = false; SkDEBUGCODE((*pathRef)->validate();) } else { int oldVCnt = (*pathRef)->countVerbs(); int oldPCnt = (*pathRef)->countPoints(); pathRef->reset(new SkPathRef); (*pathRef)->resetToSize(0, 0, 0, oldVCnt, oldPCnt); } } bool SkPathRef::operator== (const SkPathRef& ref) const { SkDEBUGCODE(this->validate();) SkDEBUGCODE(ref.validate();) // We explicitly check fSegmentMask as a quick-reject. We could skip it, // since it is only a cache of info in the fVerbs, but its a fast way to // notice a difference if (fSegmentMask != ref.fSegmentMask) { return false; } bool genIDMatch = fGenerationID && fGenerationID == ref.fGenerationID; #ifdef SK_RELEASE if (genIDMatch) { return true; } #endif if (fPoints != ref.fPoints || fConicWeights != ref.fConicWeights || fVerbs != ref.fVerbs) { SkASSERT(!genIDMatch); return false; } if (ref.fVerbs.empty()) { SkASSERT(ref.fPoints.empty()); } return true; } void SkPathRef::copy(const SkPathRef& ref, int additionalReserveVerbs, int additionalReservePoints) { SkDEBUGCODE(this->validate();) this->resetToSize(ref.fVerbs.size(), ref.fPoints.size(), ref.fConicWeights.size(), additionalReserveVerbs, additionalReservePoints); fVerbs = ref.fVerbs; fPoints = ref.fPoints; fConicWeights = ref.fConicWeights; fBoundsIsDirty = ref.fBoundsIsDirty; if (!fBoundsIsDirty) { fBounds = ref.fBounds; fIsFinite = ref.fIsFinite; } fSegmentMask = ref.fSegmentMask; fIsOval = ref.fIsOval; fIsRRect = ref.fIsRRect; fRRectOrOvalIsCCW = ref.fRRectOrOvalIsCCW; fRRectOrOvalStartIdx = ref.fRRectOrOvalStartIdx; SkDEBUGCODE(this->validate();) } void SkPathRef::interpolate(const SkPathRef& ending, SkScalar weight, SkPathRef* out) const { const SkScalar* inValues = &ending.getPoints()->fX; SkScalar* outValues = &out->getWritablePoints()->fX; int count = out->countPoints() * 2; for (int index = 0; index < count; ++index) { outValues[index] = outValues[index] * weight + inValues[index] * (1 - weight); } out->fBoundsIsDirty = true; out->fIsOval = false; out->fIsRRect = false; } std::tuple SkPathRef::growForVerbsInPath(const SkPathRef& path) { SkDEBUGCODE(this->validate();) fSegmentMask |= path.fSegmentMask; fBoundsIsDirty = true; // this also invalidates fIsFinite fIsOval = false; fIsRRect = false; if (int numVerbs = path.countVerbs()) { memcpy(fVerbs.push_back_n(numVerbs), path.fVerbs.begin(), numVerbs * sizeof(fVerbs[0])); } SkPoint* pts = nullptr; if (int numPts = path.countPoints()) { pts = fPoints.push_back_n(numPts); } SkScalar* weights = nullptr; if (int numConics = path.countWeights()) { weights = fConicWeights.push_back_n(numConics); } SkDEBUGCODE(this->validate();) return {pts, weights}; } SkPoint* SkPathRef::growForRepeatedVerb(int /*SkPath::Verb*/ verb, int numVbs, SkScalar** weights) { SkDEBUGCODE(this->validate();) int pCnt; switch (verb) { case SkPath::kMove_Verb: pCnt = numVbs; break; case SkPath::kLine_Verb: fSegmentMask |= SkPath::kLine_SegmentMask; pCnt = numVbs; break; case SkPath::kQuad_Verb: fSegmentMask |= SkPath::kQuad_SegmentMask; pCnt = 2 * numVbs; break; case SkPath::kConic_Verb: fSegmentMask |= SkPath::kConic_SegmentMask; pCnt = 2 * numVbs; break; case SkPath::kCubic_Verb: fSegmentMask |= SkPath::kCubic_SegmentMask; pCnt = 3 * numVbs; break; case SkPath::kClose_Verb: SkDEBUGFAIL("growForRepeatedVerb called for kClose_Verb"); pCnt = 0; break; case SkPath::kDone_Verb: SkDEBUGFAIL("growForRepeatedVerb called for kDone"); pCnt = 0; break; default: SkDEBUGFAIL("default should not be reached"); pCnt = 0; break; } fBoundsIsDirty = true; // this also invalidates fIsFinite fIsOval = false; fIsRRect = false; memset(fVerbs.push_back_n(numVbs), verb, numVbs); if (SkPath::kConic_Verb == verb) { SkASSERT(weights); *weights = fConicWeights.push_back_n(numVbs); } SkPoint* pts = fPoints.push_back_n(pCnt); SkDEBUGCODE(this->validate();) return pts; } SkPoint* SkPathRef::growForVerb(int /* SkPath::Verb*/ verb, SkScalar weight) { SkDEBUGCODE(this->validate();) int pCnt; unsigned mask = 0; switch (verb) { case SkPath::kMove_Verb: pCnt = 1; break; case SkPath::kLine_Verb: mask = SkPath::kLine_SegmentMask; pCnt = 1; break; case SkPath::kQuad_Verb: mask = SkPath::kQuad_SegmentMask; pCnt = 2; break; case SkPath::kConic_Verb: mask = SkPath::kConic_SegmentMask; pCnt = 2; break; case SkPath::kCubic_Verb: mask = SkPath::kCubic_SegmentMask; pCnt = 3; break; case SkPath::kClose_Verb: pCnt = 0; break; case SkPath::kDone_Verb: SkDEBUGFAIL("growForVerb called for kDone"); pCnt = 0; break; default: SkDEBUGFAIL("default is not reached"); pCnt = 0; break; } fSegmentMask |= mask; fBoundsIsDirty = true; // this also invalidates fIsFinite fIsOval = false; fIsRRect = false; fVerbs.push_back(verb); if (SkPath::kConic_Verb == verb) { fConicWeights.push_back(weight); } SkPoint* pts = fPoints.push_back_n(pCnt); SkDEBUGCODE(this->validate();) return pts; } uint32_t SkPathRef::genID(uint8_t fillType) const { SkASSERT(fEditorsAttached.load() == 0); static const uint32_t kMask = (static_cast(1) << kPathRefGenIDBitCnt) - 1; if (fGenerationID == 0) { if (fPoints.empty() && fVerbs.empty()) { fGenerationID = kEmptyGenID; } else { static std::atomic nextID{kEmptyGenID + 1}; do { fGenerationID = nextID.fetch_add(1, std::memory_order_relaxed) & kMask; } while (fGenerationID == 0 || fGenerationID == kEmptyGenID); } } #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) SkASSERT((unsigned)fillType < (1 << (32 - kPathRefGenIDBitCnt))); fGenerationID |= static_cast(fillType) << kPathRefGenIDBitCnt; #endif return fGenerationID; } void SkPathRef::addGenIDChangeListener(sk_sp listener) { if (this == gEmpty) { return; } fGenIDChangeListeners.add(std::move(listener)); } int SkPathRef::genIDChangeListenerCount() { return fGenIDChangeListeners.count(); } // we need to be called *before* the genID gets changed or zerod void SkPathRef::callGenIDChangeListeners() { fGenIDChangeListeners.changed(); } SkRRect SkPathRef::getRRect() const { const SkRect& bounds = this->getBounds(); SkVector radii[4] = {{0, 0}, {0, 0}, {0, 0}, {0, 0}}; Iter iter(*this); SkPoint pts[4]; uint8_t verb = iter.next(pts); SkASSERT(SkPath::kMove_Verb == verb); while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { if (SkPath::kConic_Verb == verb) { SkVector v1_0 = pts[1] - pts[0]; SkVector v2_1 = pts[2] - pts[1]; SkVector dxdy; if (v1_0.fX) { SkASSERT(!v2_1.fX && !v1_0.fY); dxdy.set(SkScalarAbs(v1_0.fX), SkScalarAbs(v2_1.fY)); } else if (!v1_0.fY) { SkASSERT(!v2_1.fX || !v2_1.fY); dxdy.set(SkScalarAbs(v2_1.fX), SkScalarAbs(v2_1.fY)); } else { SkASSERT(!v2_1.fY); dxdy.set(SkScalarAbs(v2_1.fX), SkScalarAbs(v1_0.fY)); } SkRRect::Corner corner = pts[1].fX == bounds.fLeft ? pts[1].fY == bounds.fTop ? SkRRect::kUpperLeft_Corner : SkRRect::kLowerLeft_Corner : pts[1].fY == bounds.fTop ? SkRRect::kUpperRight_Corner : SkRRect::kLowerRight_Corner; SkASSERT(!radii[corner].fX && !radii[corner].fY); radii[corner] = dxdy; } else { SkASSERT((verb == SkPath::kLine_Verb && (!(pts[1].fX - pts[0].fX) || !(pts[1].fY - pts[0].fY))) || verb == SkPath::kClose_Verb); } } SkRRect rrect; rrect.setRectRadii(bounds, radii); return rrect; } bool SkPathRef::isRRect(SkRRect* rrect, bool* isCCW, unsigned* start) const { if (fIsRRect) { if (rrect) { *rrect = this->getRRect(); } if (isCCW) { *isCCW = SkToBool(fRRectOrOvalIsCCW); } if (start) { *start = fRRectOrOvalStartIdx; } } return SkToBool(fIsRRect); } /////////////////////////////////////////////////////////////////////////////// SkPathRef::Iter::Iter() { #ifdef SK_DEBUG fPts = nullptr; fConicWeights = nullptr; #endif // need to init enough to make next() harmlessly return kDone_Verb fVerbs = nullptr; fVerbStop = nullptr; } SkPathRef::Iter::Iter(const SkPathRef& path) { this->setPathRef(path); } void SkPathRef::Iter::setPathRef(const SkPathRef& path) { fPts = path.points(); fVerbs = path.verbsBegin(); fVerbStop = path.verbsEnd(); fConicWeights = path.conicWeights(); if (fConicWeights) { fConicWeights -= 1; // begin one behind } // Don't allow iteration through non-finite points. if (!path.isFinite()) { fVerbStop = fVerbs; } } uint8_t SkPathRef::Iter::next(SkPoint pts[4]) { SkASSERT(pts); SkDEBUGCODE(unsigned peekResult = this->peek();) if (fVerbs == fVerbStop) { SkASSERT(peekResult == SkPath::kDone_Verb); return (uint8_t) SkPath::kDone_Verb; } // fVerbs points one beyond next verb so decrement first. unsigned verb = *fVerbs++; const SkPoint* srcPts = fPts; switch (verb) { case SkPath::kMove_Verb: pts[0] = srcPts[0]; srcPts += 1; break; case SkPath::kLine_Verb: pts[0] = srcPts[-1]; pts[1] = srcPts[0]; srcPts += 1; break; case SkPath::kConic_Verb: fConicWeights += 1; [[fallthrough]]; case SkPath::kQuad_Verb: pts[0] = srcPts[-1]; pts[1] = srcPts[0]; pts[2] = srcPts[1]; srcPts += 2; break; case SkPath::kCubic_Verb: pts[0] = srcPts[-1]; pts[1] = srcPts[0]; pts[2] = srcPts[1]; pts[3] = srcPts[2]; srcPts += 3; break; case SkPath::kClose_Verb: break; case SkPath::kDone_Verb: SkASSERT(fVerbs == fVerbStop); break; } fPts = srcPts; SkASSERT(peekResult == verb); return (uint8_t) verb; } uint8_t SkPathRef::Iter::peek() const { return fVerbs < fVerbStop ? *fVerbs : (uint8_t) SkPath::kDone_Verb; } bool SkPathRef::isValid() const { if (fIsOval || fIsRRect) { // Currently we don't allow both of these to be set, even though ovals are ro if (fIsOval == fIsRRect) { return false; } if (fIsOval) { if (fRRectOrOvalStartIdx >= 4) { return false; } } else { if (fRRectOrOvalStartIdx >= 8) { return false; } } } if (!fBoundsIsDirty && !fBounds.isEmpty()) { bool isFinite = true; auto leftTop = skvx::float2(fBounds.fLeft, fBounds.fTop); auto rightBot = skvx::float2(fBounds.fRight, fBounds.fBottom); for (int i = 0; i < fPoints.size(); ++i) { auto point = skvx::float2(fPoints[i].fX, fPoints[i].fY); #ifdef SK_DEBUG if (fPoints[i].isFinite() && (any(point < leftTop)|| any(point > rightBot))) { SkDebugf("bad SkPathRef bounds: %g %g %g %g\n", fBounds.fLeft, fBounds.fTop, fBounds.fRight, fBounds.fBottom); for (int j = 0; j < fPoints.size(); ++j) { if (i == j) { SkDebugf("*** bounds do not contain: "); } SkDebugf("%g %g\n", fPoints[j].fX, fPoints[j].fY); } return false; } #endif if (fPoints[i].isFinite() && any(point < leftTop) && !any(point > rightBot)) return false; if (!fPoints[i].isFinite()) { isFinite = false; } } if (SkToBool(fIsFinite) != isFinite) { return false; } } return true; } void SkPathRef::reset() { commonReset(); fPoints.clear(); fVerbs.clear(); fConicWeights.clear(); SkDEBUGCODE(validate();) } bool SkPathRef::dataMatchesVerbs() const { const auto info = sk_path_analyze_verbs(fVerbs.begin(), fVerbs.size()); return info.valid && info.segmentMask == fSegmentMask && info.points == fPoints.size() && info.weights == fConicWeights.size(); } /////////////////////////////////////////////////////////////////////////////// void SkPoint::scale(float scale, SkPoint* dst) const { SkASSERT(dst); dst->set(fX * scale, fY * scale); } bool SkPoint::normalize() { return this->setLength(fX, fY, 1); } bool SkPoint::setNormalize(float x, float y) { return this->setLength(x, y, 1); } bool SkPoint::setLength(float length) { return this->setLength(fX, fY, length); } /* * We have to worry about 2 tricky conditions: * 1. underflow of mag2 (compared against nearlyzero^2) * 2. overflow of mag2 (compared w/ isfinite) * * If we underflow, we return false. If we overflow, we compute again using * doubles, which is much slower (3x in a desktop test) but will not overflow. */ template bool set_point_length(SkPoint* pt, float x, float y, float length, float* orig_length = nullptr) { SkASSERT(!use_rsqrt || (orig_length == nullptr)); // our mag2 step overflowed to infinity, so use doubles instead. // much slower, but needed when x or y are very large, other wise we // divide by inf. and return (0,0) vector. double xx = x; double yy = y; double dmag = sqrt(xx * xx + yy * yy); double dscale = sk_ieee_double_divide(length, dmag); x *= dscale; y *= dscale; // check if we're not finite, or we're zero-length if (!sk_float_isfinite(x) || !sk_float_isfinite(y) || (x == 0 && y == 0)) { pt->set(0, 0); return false; } float mag = 0; if (orig_length) { mag = sk_double_to_float(dmag); } pt->set(x, y); if (orig_length) { *orig_length = mag; } return true; } float SkPoint::Normalize(SkPoint* pt) { float mag; if (set_point_length(pt, pt->fX, pt->fY, 1.0f, &mag)) { return mag; } return 0; } float SkPoint::Length(float dx, float dy) { float mag2 = dx * dx + dy * dy; if (sk_float_isfinite(mag2)) { return sk_float_sqrt(mag2); } else { double xx = dx; double yy = dy; return sk_double_to_float(sqrt(xx * xx + yy * yy)); } } bool SkPoint::setLength(float x, float y, float length) { return set_point_length(this, x, y, length); } bool SkPointPriv::SetLengthFast(SkPoint* pt, float length) { return set_point_length(pt, pt->fX, pt->fY, length); } /////////////////////////////////////////////////////////////////////////////// float SkPointPriv::DistanceToLineBetweenSqd(const SkPoint& pt, const SkPoint& a, const SkPoint& b, Side* side) { SkVector u = b - a; SkVector v = pt - a; float uLengthSqd = LengthSqd(u); float det = u.cross(v); if (side) { SkASSERT(-1 == kLeft_Side && 0 == kOn_Side && 1 == kRight_Side); *side = (Side)sk_float_sgn(det); } float temp = sk_ieee_float_divide(det, uLengthSqd); temp *= det; // It's possible we have a degenerate line vector, or we're so far away it looks degenerate // In this case, return squared distance to point A. if (!sk_float_isfinite(temp)) { return LengthSqd(v); } return temp; } float SkPointPriv::DistanceToLineSegmentBetweenSqd(const SkPoint& pt, const SkPoint& a, const SkPoint& b) { // See comments to distanceToLineBetweenSqd. If the projection of c onto // u is between a and b then this returns the same result as that // function. Otherwise, it returns the distance to the closest of a and // b. Let the projection of v onto u be v'. There are three cases: // 1. v' points opposite to u. c is not between a and b and is closer // to a than b. // 2. v' points along u and has magnitude less than y. c is between // a and b and the distance to the segment is the same as distance // to the line ab. // 3. v' points along u and has greater magnitude than u. c is not // between a and b and is closer to b than a. // v' = (u dot v) * u / |u|. So if (u dot v)/|u| is less than zero we're // in case 1. If (u dot v)/|u| is > |u| we are in case 3. Otherwise, // we're in case 2. We actually compare (u dot v) to 0 and |u|^2 to // avoid a sqrt to compute |u|. SkVector u = b - a; SkVector v = pt - a; float uLengthSqd = LengthSqd(u); float uDotV = SkPoint::DotProduct(u, v); // closest point is point A if (uDotV <= 0) { return LengthSqd(v); // closest point is point B } else if (uDotV > uLengthSqd) { return DistanceToSqd(b, pt); // closest point is inside segment } else { float det = u.cross(v); float temp = sk_ieee_float_divide(det, uLengthSqd); temp *= det; // It's possible we have a degenerate segment, or we're so far away it looks degenerate // In this case, return squared distance to point A. if (!sk_float_isfinite(temp)) { return LengthSqd(v); } return temp; } } class SkMatrix; bool SkIRect::intersect(const SkIRect& a, const SkIRect& b) { SkIRect tmp = { std::max(a.fLeft, b.fLeft), std::max(a.fTop, b.fTop), std::min(a.fRight, b.fRight), std::min(a.fBottom, b.fBottom) }; if (tmp.isEmpty()) { return false; } *this = tmp; return true; } void SkIRect::join(const SkIRect& r) { // do nothing if the params are empty if (r.fLeft >= r.fRight || r.fTop >= r.fBottom) { return; } // if we are empty, just assign if (fLeft >= fRight || fTop >= fBottom) { *this = r; } else { if (r.fLeft < fLeft) fLeft = r.fLeft; if (r.fTop < fTop) fTop = r.fTop; if (r.fRight > fRight) fRight = r.fRight; if (r.fBottom > fBottom) fBottom = r.fBottom; } } ///////////////////////////////////////////////////////////////////////////// void SkRect::toQuad(SkPoint quad[4]) const { SkASSERT(quad); quad[0].set(fLeft, fTop); quad[1].set(fRight, fTop); quad[2].set(fRight, fBottom); quad[3].set(fLeft, fBottom); } bool SkRect::setBoundsCheck(const SkPoint pts[], int count) { SkASSERT((pts && count > 0) || count == 0); if (count <= 0) { this->setEmpty(); return true; } skvx::float4 min, max; if (count & 1) { min = max = skvx::float2::Load(pts).xyxy(); pts += 1; count -= 1; } else { min = max = skvx::float4::Load(pts); pts += 2; count -= 2; } skvx::float4 accum = min * 0; while (count) { skvx::float4 xy = skvx::float4::Load(pts); accum = accum * xy; min = skvx::min(min, xy); max = skvx::max(max, xy); pts += 2; count -= 2; } const bool all_finite = all(accum * 0 == 0); if (all_finite) { this->setLTRB(std::min(min[0], min[2]), std::min(min[1], min[3]), std::max(max[0], max[2]), std::max(max[1], max[3])); } else { this->setEmpty(); } return all_finite; } void SkRect::setBoundsNoCheck(const SkPoint pts[], int count) { if (!this->setBoundsCheck(pts, count)) { this->setLTRB(SK_FloatNaN, SK_FloatNaN, SK_FloatNaN, SK_FloatNaN); } } #define CHECK_INTERSECT(al, at, ar, ab, bl, bt, br, bb) \ float L = std::max(al, bl); \ float R = std::min(ar, br); \ float T = std::max(at, bt); \ float B = std::min(ab, bb); \ do { if (!(L < R && T < B)) return false; } while (0) // do the !(opposite) check so we return false if either arg is NaN bool SkRect::intersect(const SkRect& r) { CHECK_INTERSECT(r.fLeft, r.fTop, r.fRight, r.fBottom, fLeft, fTop, fRight, fBottom); this->setLTRB(L, T, R, B); return true; } bool SkRect::intersect(const SkRect& a, const SkRect& b) { CHECK_INTERSECT(a.fLeft, a.fTop, a.fRight, a.fBottom, b.fLeft, b.fTop, b.fRight, b.fBottom); this->setLTRB(L, T, R, B); return true; } void SkRect::join(const SkRect& r) { if (r.isEmpty()) { return; } if (this->isEmpty()) { *this = r; } else { fLeft = std::min(fLeft, r.fLeft); fTop = std::min(fTop, r.fTop); fRight = std::max(fRight, r.fRight); fBottom = std::max(fBottom, r.fBottom); } } //////////////////////////////////////////////////////////////////////////////////////////////// static const char* set_scalar(SkString* storage, float value, SkScalarAsStringType asType) { storage->reset(); SkAppendScalar(storage, value, asType); return storage->c_str(); } void SkRect::dump(bool asHex) const { SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType; SkString line; if (asHex) { SkString tmp; line.printf( "SkRect::MakeLTRB(%s, /* %f */\n", set_scalar(&tmp, fLeft, asType), fLeft); line.appendf(" %s, /* %f */\n", set_scalar(&tmp, fTop, asType), fTop); line.appendf(" %s, /* %f */\n", set_scalar(&tmp, fRight, asType), fRight); line.appendf(" %s /* %f */);", set_scalar(&tmp, fBottom, asType), fBottom); } else { SkString strL, strT, strR, strB; SkAppendScalarDec(&strL, fLeft); SkAppendScalarDec(&strT, fTop); SkAppendScalarDec(&strR, fRight); SkAppendScalarDec(&strB, fBottom); line.printf("SkRect::MakeLTRB(%s, %s, %s, %s);", strL.c_str(), strT.c_str(), strR.c_str(), strB.c_str()); } SkDebugf("%s\n", line.c_str()); } //////////////////////////////////////////////////////////////////////////////////////////////// template static bool subtract(const R& a, const R& b, R* out) { if (a.isEmpty() || b.isEmpty() || !R::Intersects(a, b)) { // Either already empty, or subtracting the empty rect, or there's no intersection, so // in all cases the answer is A. *out = a; return true; } // 4 rectangles to consider. If the edge in A is contained in B, the resulting difference can // be represented exactly as a rectangle. Otherwise the difference is the largest subrectangle // that is disjoint from B: // 1. Left part of A: (A.left, A.top, B.left, A.bottom) // 2. Right part of A: (B.right, A.top, A.right, A.bottom) // 3. Top part of A: (A.left, A.top, A.right, B.top) // 4. Bottom part of A: (A.left, B.bottom, A.right, A.bottom) // // Depending on how B intersects A, there will be 1 to 4 positive areas: // - 4 occur when A contains B // - 3 occur when B intersects a single edge // - 2 occur when B intersects at a corner, or spans two opposing edges // - 1 occurs when B spans two opposing edges and contains a 3rd, resulting in an exact rect // - 0 occurs when B contains A, resulting in the empty rect // // Compute the relative areas of the 4 rects described above. Since each subrectangle shares // either the width or height of A, we only have to divide by the other dimension, which avoids // overflow on int32 types, and even if the float relative areas overflow to infinity, the // comparisons work out correctly and (one of) the infinitely large subrects will be chosen. float aHeight = (float) a.height(); float aWidth = (float) a.width(); float leftArea = 0.f, rightArea = 0.f, topArea = 0.f, bottomArea = 0.f; int positiveCount = 0; if (b.fLeft > a.fLeft) { leftArea = (b.fLeft - a.fLeft) / aWidth; positiveCount++; } if (a.fRight > b.fRight) { rightArea = (a.fRight - b.fRight) / aWidth; positiveCount++; } if (b.fTop > a.fTop) { topArea = (b.fTop - a.fTop) / aHeight; positiveCount++; } if (a.fBottom > b.fBottom) { bottomArea = (a.fBottom - b.fBottom) / aHeight; positiveCount++; } if (positiveCount == 0) { SkASSERT(b.contains(a)); *out = R::MakeEmpty(); return true; } *out = a; if (leftArea > rightArea && leftArea > topArea && leftArea > bottomArea) { // Left chunk of A, so the new right edge is B's left edge out->fRight = b.fLeft; } else if (rightArea > topArea && rightArea > bottomArea) { // Right chunk of A, so the new left edge is B's right edge out->fLeft = b.fRight; } else if (topArea > bottomArea) { // Top chunk of A, so the new bottom edge is B's top edge out->fBottom = b.fTop; } else { // Bottom chunk of A, so the new top edge is B's bottom edge SkASSERT(bottomArea > 0.f); out->fTop = b.fBottom; } // If we have 1 valid area, the disjoint shape is representable as a rectangle. SkASSERT(!R::Intersects(*out, b)); return positiveCount == 1; } bool SkRectPriv::Subtract(const SkRect& a, const SkRect& b, SkRect* out) { return subtract(a, b, out); } bool SkRectPriv::Subtract(const SkIRect& a, const SkIRect& b, SkIRect* out) { return subtract(a, b, out); } bool SkRectPriv::QuadContainsRect(const SkMatrix& m, const SkIRect& a, const SkIRect& b) { return QuadContainsRect(SkM44(m), SkRect::Make(a), SkRect::Make(b)); } bool SkRectPriv::QuadContainsRect(const SkM44& m, const SkRect& a, const SkRect& b) { SkDEBUGCODE(SkM44 inverse;) SkASSERT(m.invert(&inverse)); // With empty rectangles, the calculated edges could give surprising results. If 'a' were not // sorted, its normals would point outside the sorted rectangle, so lots of potential rects // would be seen as "contained". If 'a' is all 0s, its edge equations are also (0,0,0) so every // point has a distance of 0, and would be interpreted as inside. if (a.isEmpty()) { return false; } // However, 'b' is only used to define its 4 corners to check against the transformed edges. // This is valid regardless of b's emptiness or sortedness. // Calculate the 4 homogenous coordinates of 'a' transformed by 'm' where Z=0 and W=1. auto ax = skvx::float4{a.fLeft, a.fRight, a.fRight, a.fLeft}; auto ay = skvx::float4{a.fTop, a.fTop, a.fBottom, a.fBottom}; auto max = m.rc(0,0)*ax + m.rc(0,1)*ay + m.rc(0,3); auto may = m.rc(1,0)*ax + m.rc(1,1)*ay + m.rc(1,3); auto maw = m.rc(3,0)*ax + m.rc(3,1)*ay + m.rc(3,3); if (all(maw < 0.f)) { // If all points of A are mapped to w < 0, then the edge equations end up representing the // convex hull of projected points when A should in fact be considered empty. return false; } // Cross product of adjacent vertices provides homogenous lines for the 4 sides of the quad auto lA = may*skvx::shuffle<1,2,3,0>(maw) - maw*skvx::shuffle<1,2,3,0>(may); auto lB = maw*skvx::shuffle<1,2,3,0>(max) - max*skvx::shuffle<1,2,3,0>(maw); auto lC = max*skvx::shuffle<1,2,3,0>(may) - may*skvx::shuffle<1,2,3,0>(max); // Before transforming, the corners of 'a' were in CW order, but afterwards they may become CCW, // so the sign corrects the direction of the edge normals to point inwards. float sign = (lA[0]*lB[1] - lB[0]*lA[1]) < 0 ? -1.f : 1.f; // Calculate distance from 'b' to each edge. Since 'b' has presumably been transformed by 'm' // *and* projected, this assumes W = 1. auto d0 = sign * (lA*b.fLeft + lB*b.fTop + lC); auto d1 = sign * (lA*b.fRight + lB*b.fTop + lC); auto d2 = sign * (lA*b.fRight + lB*b.fBottom + lC); auto d3 = sign * (lA*b.fLeft + lB*b.fBottom + lC); // 'b' is contained in the mapped rectangle if all distances are >= 0 return all((d0 >= 0.f) & (d1 >= 0.f) & (d2 >= 0.f) & (d3 >= 0.f)); } SkIRect SkRectPriv::ClosestDisjointEdge(const SkIRect& src, const SkIRect& dst) { if (src.isEmpty() || dst.isEmpty()) { return SkIRect::MakeEmpty(); } int l = src.fLeft; int r = src.fRight; if (r <= dst.fLeft) { // Select right column of pixels in crop l = r - 1; } else if (l >= dst.fRight) { // Left column of 'crop' r = l + 1; } else { // Regular intersection along X axis. l = SkTPin(l, dst.fLeft, dst.fRight); r = SkTPin(r, dst.fLeft, dst.fRight); } int t = src.fTop; int b = src.fBottom; if (b <= dst.fTop) { // Select bottom row of pixels in crop t = b - 1; } else if (t >= dst.fBottom) { // Top row of 'crop' b = t + 1; } else { t = SkTPin(t, dst.fTop, dst.fBottom); b = SkTPin(b, dst.fTop, dst.fBottom); } return SkIRect::MakeLTRB(l,t,r,b); } /////////////////////////////////////////////////////////////////////////////// void SkRRect::setOval(const SkRect& oval) { if (!this->initializeRect(oval)) { return; } SkScalar xRad = SkRectPriv::HalfWidth(fRect); SkScalar yRad = SkRectPriv::HalfHeight(fRect); if (xRad == 0.0f || yRad == 0.0f) { // All the corners will be square memset(fRadii, 0, sizeof(fRadii)); fType = kRect_Type; } else { for (int i = 0; i < 4; ++i) { fRadii[i].set(xRad, yRad); } fType = kOval_Type; } SkASSERT(this->isValid()); } void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { if (!this->initializeRect(rect)) { return; } if (!SkScalarsAreFinite(xRad, yRad)) { xRad = yRad = 0; // devolve into a simple rect } if (fRect.width() < xRad+xRad || fRect.height() < yRad+yRad) { // At most one of these two divides will be by zero, and neither numerator is zero. SkScalar scale = std::min(sk_ieee_float_divide(fRect. width(), xRad + xRad), sk_ieee_float_divide(fRect.height(), yRad + yRad)); SkASSERT(scale < SK_Scalar1); xRad *= scale; yRad *= scale; } if (xRad <= 0 || yRad <= 0) { // all corners are square in this case this->setRect(rect); return; } for (int i = 0; i < 4; ++i) { fRadii[i].set(xRad, yRad); } fType = kSimple_Type; if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) { fType = kOval_Type; // TODO: assert that all the x&y radii are already W/2 & H/2 } SkASSERT(this->isValid()); } void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad, SkScalar rightRad, SkScalar bottomRad) { if (!this->initializeRect(rect)) { return; } const SkScalar array[4] = { leftRad, topRad, rightRad, bottomRad }; if (!SkScalarsAreFinite(array, 4)) { this->setRect(rect); // devolve into a simple rect return; } leftRad = std::max(leftRad, 0.0f); topRad = std::max(topRad, 0.0f); rightRad = std::max(rightRad, 0.0f); bottomRad = std::max(bottomRad, 0.0f); SkScalar scale = SK_Scalar1; if (leftRad + rightRad > fRect.width()) { scale = fRect.width() / (leftRad + rightRad); } if (topRad + bottomRad > fRect.height()) { scale = std::min(scale, fRect.height() / (topRad + bottomRad)); } if (scale < SK_Scalar1) { leftRad *= scale; topRad *= scale; rightRad *= scale; bottomRad *= scale; } if (leftRad == rightRad && topRad == bottomRad) { if (leftRad >= SkScalarHalf(fRect.width()) && topRad >= SkScalarHalf(fRect.height())) { fType = kOval_Type; } else if (0 == leftRad || 0 == topRad) { // If the left and (by equality check above) right radii are zero then it is a rect. // Same goes for top/bottom. fType = kRect_Type; leftRad = 0; topRad = 0; rightRad = 0; bottomRad = 0; } else { fType = kSimple_Type; } } else { fType = kNinePatch_Type; } fRadii[kUpperLeft_Corner].set(leftRad, topRad); fRadii[kUpperRight_Corner].set(rightRad, topRad); fRadii[kLowerRight_Corner].set(rightRad, bottomRad); fRadii[kLowerLeft_Corner].set(leftRad, bottomRad); SkASSERT(this->isValid()); } // These parameters intentionally double. Apropos crbug.com/463920, if one of the // radii is huge while the other is small, single precision math can completely // miss the fact that a scale is required. static double compute_min_scale(double rad1, double rad2, double limit, double curMin) { if ((rad1 + rad2) > limit) { return std::min(curMin, limit / (rad1 + rad2)); } return curMin; } static bool clamp_to_zero(SkVector radii[4]) { bool allCornersSquare = true; // Clamp negative radii to zero for (int i = 0; i < 4; ++i) { if (radii[i].fX <= 0 || radii[i].fY <= 0) { // In this case we are being a little fast & loose. Since one of // the radii is 0 the corner is square. However, the other radii // could still be non-zero and play in the global scale factor // computation. radii[i].fX = 0; radii[i].fY = 0; } else { allCornersSquare = false; } } return allCornersSquare; } void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) { if (!this->initializeRect(rect)) { return; } if (!SkScalarsAreFinite(&radii[0].fX, 8)) { this->setRect(rect); // devolve into a simple rect return; } memcpy(fRadii, radii, sizeof(fRadii)); if (clamp_to_zero(fRadii)) { this->setRect(rect); return; } this->scaleRadii(); if (!this->isValid()) { this->setRect(rect); return; } } bool SkRRect::initializeRect(const SkRect& rect) { // Check this before sorting because sorting can hide nans. if (!rect.isFinite()) { *this = SkRRect(); return false; } fRect = rect.makeSorted(); if (fRect.isEmpty()) { memset(fRadii, 0, sizeof(fRadii)); fType = kEmpty_Type; return false; } return true; } // If we can't distinguish one of the radii relative to the other, force it to zero so it // doesn't confuse us later. See crbug.com/850350 // static void flush_to_zero(SkScalar& a, SkScalar& b) { SkASSERT(a >= 0); SkASSERT(b >= 0); if (a + b == a) { b = 0; } else if (a + b == b) { a = 0; } } bool SkRRect::scaleRadii() { // Proportionally scale down all radii to fit. Find the minimum ratio // of a side and the radii on that side (for all four sides) and use // that to scale down _all_ the radii. This algorithm is from the // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping // Curves: // "Let f = min(Li/Si), where i is one of { top, right, bottom, left }, // Si is the sum of the two corresponding radii of the corners on side i, // and Ltop = Lbottom = the width of the box, // and Lleft = Lright = the height of the box. // If f < 1, then all corner radii are reduced by multiplying them by f." double scale = 1.0; // The sides of the rectangle may be larger than a float. double width = (double)fRect.fRight - (double)fRect.fLeft; double height = (double)fRect.fBottom - (double)fRect.fTop; scale = compute_min_scale(fRadii[0].fX, fRadii[1].fX, width, scale); scale = compute_min_scale(fRadii[1].fY, fRadii[2].fY, height, scale); scale = compute_min_scale(fRadii[2].fX, fRadii[3].fX, width, scale); scale = compute_min_scale(fRadii[3].fY, fRadii[0].fY, height, scale); flush_to_zero(fRadii[0].fX, fRadii[1].fX); flush_to_zero(fRadii[1].fY, fRadii[2].fY); flush_to_zero(fRadii[2].fX, fRadii[3].fX); flush_to_zero(fRadii[3].fY, fRadii[0].fY); if (scale < 1.0) { SkScaleToSides::AdjustRadii(width, scale, &fRadii[0].fX, &fRadii[1].fX); SkScaleToSides::AdjustRadii(height, scale, &fRadii[1].fY, &fRadii[2].fY); SkScaleToSides::AdjustRadii(width, scale, &fRadii[2].fX, &fRadii[3].fX); SkScaleToSides::AdjustRadii(height, scale, &fRadii[3].fY, &fRadii[0].fY); } // adjust radii may set x or y to zero; set companion to zero as well clamp_to_zero(fRadii); // May be simple, oval, or complex, or become a rect/empty if the radii adjustment made them 0 this->computeType(); // TODO: Why can't we assert this here? //SkASSERT(this->isValid()); return scale < 1.0; } // This method determines if a point known to be inside the RRect's bounds is // inside all the corners. bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const { SkPoint canonicalPt; // (x,y) translated to one of the quadrants int index; if (kOval_Type == this->type()) { canonicalPt.set(x - fRect.centerX(), y - fRect.centerY()); index = kUpperLeft_Corner; // any corner will do in this case } else { if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX && y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) { // UL corner index = kUpperLeft_Corner; canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX), y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY)); SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0); } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX && y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) { // LL corner index = kLowerLeft_Corner; canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX), y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY)); SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0); } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX && y < fRect.fTop + fRadii[kUpperRight_Corner].fY) { // UR corner index = kUpperRight_Corner; canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX), y - (fRect.fTop + fRadii[kUpperRight_Corner].fY)); SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0); } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX && y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) { // LR corner index = kLowerRight_Corner; canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX), y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY)); SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0); } else { // not in any of the corners return true; } } // A point is in an ellipse (in standard position) if: // x^2 y^2 // ----- + ----- <= 1 // a^2 b^2 // or : // b^2*x^2 + a^2*y^2 <= (ab)^2 SkScalar dist = SkScalarSquare(canonicalPt.fX) * SkScalarSquare(fRadii[index].fY) + SkScalarSquare(canonicalPt.fY) * SkScalarSquare(fRadii[index].fX); return dist <= SkScalarSquare(fRadii[index].fX * fRadii[index].fY); } bool SkRRectPriv::IsNearlySimpleCircular(const SkRRect& rr, SkScalar tolerance) { SkScalar simpleRadius = rr.fRadii[0].fX; return SkScalarNearlyEqual(simpleRadius, rr.fRadii[0].fY, tolerance) && SkScalarNearlyEqual(simpleRadius, rr.fRadii[1].fX, tolerance) && SkScalarNearlyEqual(simpleRadius, rr.fRadii[1].fY, tolerance) && SkScalarNearlyEqual(simpleRadius, rr.fRadii[2].fX, tolerance) && SkScalarNearlyEqual(simpleRadius, rr.fRadii[2].fY, tolerance) && SkScalarNearlyEqual(simpleRadius, rr.fRadii[3].fX, tolerance) && SkScalarNearlyEqual(simpleRadius, rr.fRadii[3].fY, tolerance); } bool SkRRectPriv::AllCornersCircular(const SkRRect& rr, SkScalar tolerance) { return SkScalarNearlyEqual(rr.fRadii[0].fX, rr.fRadii[0].fY, tolerance) && SkScalarNearlyEqual(rr.fRadii[1].fX, rr.fRadii[1].fY, tolerance) && SkScalarNearlyEqual(rr.fRadii[2].fX, rr.fRadii[2].fY, tolerance) && SkScalarNearlyEqual(rr.fRadii[3].fX, rr.fRadii[3].fY, tolerance); } bool SkRRect::contains(const SkRect& rect) const { if (!this->getBounds().contains(rect)) { // If 'rect' isn't contained by the RR's bounds then the // RR definitely doesn't contain it return false; } if (this->isRect()) { // the prior test was sufficient return true; } // At this point we know all four corners of 'rect' are inside the // bounds of of this RR. Check to make sure all the corners are inside // all the curves return this->checkCornerContainment(rect.fLeft, rect.fTop) && this->checkCornerContainment(rect.fRight, rect.fTop) && this->checkCornerContainment(rect.fRight, rect.fBottom) && this->checkCornerContainment(rect.fLeft, rect.fBottom); } static bool radii_are_nine_patch(const SkVector radii[4]) { return radii[SkRRect::kUpperLeft_Corner].fX == radii[SkRRect::kLowerLeft_Corner].fX && radii[SkRRect::kUpperLeft_Corner].fY == radii[SkRRect::kUpperRight_Corner].fY && radii[SkRRect::kUpperRight_Corner].fX == radii[SkRRect::kLowerRight_Corner].fX && radii[SkRRect::kLowerLeft_Corner].fY == radii[SkRRect::kLowerRight_Corner].fY; } // There is a simplified version of this method in setRectXY void SkRRect::computeType() { if (fRect.isEmpty()) { SkASSERT(fRect.isSorted()); for (size_t i = 0; i < std::size(fRadii); ++i) { SkASSERT((fRadii[i] == SkVector{0, 0})); } fType = kEmpty_Type; SkASSERT(this->isValid()); return; } bool allRadiiEqual = true; // are all x radii equal and all y radii? bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY; for (int i = 1; i < 4; ++i) { if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { // if either radius is zero the corner is square so both have to // be non-zero to have a rounded corner allCornersSquare = false; } if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { allRadiiEqual = false; } } if (allCornersSquare) { fType = kRect_Type; SkASSERT(this->isValid()); return; } if (allRadiiEqual) { if (fRadii[0].fX >= SkScalarHalf(fRect.width()) && fRadii[0].fY >= SkScalarHalf(fRect.height())) { fType = kOval_Type; } else { fType = kSimple_Type; } SkASSERT(this->isValid()); return; } if (radii_are_nine_patch(fRadii)) { fType = kNinePatch_Type; } else { fType = kComplex_Type; } if (!this->isValid()) { this->setRect(this->rect()); SkASSERT(this->isValid()); } } bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const { if (nullptr == dst) { return false; } // Assert that the caller is not trying to do this in place, which // would violate const-ness. Do not return false though, so that // if they know what they're doing and want to violate it they can. SkASSERT(dst != this); if (matrix.isIdentity()) { *dst = *this; return true; } if (!matrix.preservesAxisAlignment()) { return false; } SkRect newRect; if (!matrix.mapRect(&newRect, fRect)) { return false; } // The matrix may have scaled us to zero (or due to float madness, we now have collapsed // some dimension of the rect, so we need to check for that. Note that matrix must be // scale and translate and mapRect() produces a sorted rect. So an empty rect indicates // loss of precision. if (!newRect.isFinite() || newRect.isEmpty()) { return false; } // At this point, this is guaranteed to succeed, so we can modify dst. dst->fRect = newRect; // Since the only transforms that were allowed are axis aligned, the type // remains unchanged. dst->fType = fType; if (kRect_Type == fType) { SkASSERT(dst->isValid()); return true; } if (kOval_Type == fType) { for (int i = 0; i < 4; ++i) { dst->fRadii[i].fX = SkScalarHalf(newRect.width()); dst->fRadii[i].fY = SkScalarHalf(newRect.height()); } SkASSERT(dst->isValid()); return true; } // Now scale each corner SkScalar xScale = matrix.getScaleX(); SkScalar yScale = matrix.getScaleY(); // There is a rotation of 90 (Clockwise 90) or 270 (Counter clockwise 90). // 180 degrees rotations are simply flipX with a flipY and would come under // a scale transform. if (!matrix.isScaleTranslate()) { const bool isClockwise = matrix.getSkewX() < 0; // The matrix location for scale changes if there is a rotation. xScale = matrix.getSkewY() * (isClockwise ? 1 : -1); yScale = matrix.getSkewX() * (isClockwise ? -1 : 1); const int dir = isClockwise ? 3 : 1; for (int i = 0; i < 4; ++i) { const int src = (i + dir) >= 4 ? (i + dir) % 4 : (i + dir); // Swap X and Y axis for the radii. dst->fRadii[i].fX = fRadii[src].fY; dst->fRadii[i].fY = fRadii[src].fX; } } else { for (int i = 0; i < 4; ++i) { dst->fRadii[i].fX = fRadii[i].fX; dst->fRadii[i].fY = fRadii[i].fY; } } const bool flipX = xScale < 0; if (flipX) { xScale = -xScale; } const bool flipY = yScale < 0; if (flipY) { yScale = -yScale; } // Scale the radii without respecting the flip. for (int i = 0; i < 4; ++i) { dst->fRadii[i].fX *= xScale; dst->fRadii[i].fY *= yScale; } // Now swap as necessary. using std::swap; if (flipX) { if (flipY) { // Swap with opposite corners swap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]); swap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]); } else { // Only swap in x swap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]); swap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]); } } else if (flipY) { // Only swap in y swap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]); swap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]); } if (!AreRectAndRadiiValid(dst->fRect, dst->fRadii)) { return false; } dst->scaleRadii(); dst->isValid(); // TODO: is this meant to be SkASSERT(dst->isValid())? return true; } /////////////////////////////////////////////////////////////////////////////// void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const { SkRect r = fRect.makeInset(dx, dy); bool degenerate = false; if (r.fRight <= r.fLeft) { degenerate = true; r.fLeft = r.fRight = SkScalarAve(r.fLeft, r.fRight); } if (r.fBottom <= r.fTop) { degenerate = true; r.fTop = r.fBottom = SkScalarAve(r.fTop, r.fBottom); } if (degenerate) { dst->fRect = r; memset(dst->fRadii, 0, sizeof(dst->fRadii)); dst->fType = kEmpty_Type; return; } if (!r.isFinite()) { *dst = SkRRect(); return; } SkVector radii[4]; memcpy(radii, fRadii, sizeof(radii)); for (int i = 0; i < 4; ++i) { if (radii[i].fX) { radii[i].fX -= dx; } if (radii[i].fY) { radii[i].fY -= dy; } } dst->setRectRadii(r, radii); } /////////////////////////////////////////////////////////////////////////////// size_t SkRRect::writeToMemory(void* buffer) const { // Serialize only the rect and corners, but not the derived type tag. memcpy(buffer, this, kSizeInMemory); return kSizeInMemory; } void SkRRectPriv::WriteToBuffer(const SkRRect& rr, SkWBuffer* buffer) { // Serialize only the rect and corners, but not the derived type tag. buffer->write(&rr, SkRRect::kSizeInMemory); } size_t SkRRect::readFromMemory(const void* buffer, size_t length) { if (length < kSizeInMemory) { return 0; } // The extra (void*) tells GCC not to worry that kSizeInMemory < sizeof(SkRRect). SkRRect raw; memcpy((void*)&raw, buffer, kSizeInMemory); this->setRectRadii(raw.fRect, raw.fRadii); return kSizeInMemory; } bool SkRRectPriv::ReadFromBuffer(SkRBuffer* buffer, SkRRect* rr) { if (buffer->available() < SkRRect::kSizeInMemory) { return false; } SkRRect storage; return buffer->read(&storage, SkRRect::kSizeInMemory) && (rr->readFromMemory(&storage, SkRRect::kSizeInMemory) == SkRRect::kSizeInMemory); } SkString SkRRect::dumpToString(bool asHex) const { SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType; fRect.dump(asHex); SkString line("const SkPoint corners[] = {\n"); for (int i = 0; i < 4; ++i) { SkString strX, strY; SkAppendScalar(&strX, fRadii[i].x(), asType); SkAppendScalar(&strY, fRadii[i].y(), asType); line.appendf(" { %s, %s },", strX.c_str(), strY.c_str()); if (asHex) { line.appendf(" /* %f %f */", fRadii[i].x(), fRadii[i].y()); } line.append("\n"); } line.append("};"); return line; } void SkRRect::dump(bool asHex) const { SkDebugf("%s\n", this->dumpToString(asHex).c_str()); } /////////////////////////////////////////////////////////////////////////////// /** * We need all combinations of predicates to be true to have a "safe" radius value. */ static bool are_radius_check_predicates_valid(SkScalar rad, SkScalar min, SkScalar max) { return (min <= max) && (rad <= max - min) && (min + rad <= max) && (max - rad >= min) && rad >= 0; } bool SkRRect::isValid() const { if (!AreRectAndRadiiValid(fRect, fRadii)) { return false; } bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY); bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY); bool allRadiiSame = true; for (int i = 1; i < 4; ++i) { if (0 != fRadii[i].fX || 0 != fRadii[i].fY) { allRadiiZero = false; } if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { allRadiiSame = false; } if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { allCornersSquare = false; } } bool patchesOfNine = radii_are_nine_patch(fRadii); if (fType < 0 || fType > kLastType) { return false; } switch (fType) { case kEmpty_Type: if (!fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) { return false; } break; case kRect_Type: if (fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) { return false; } break; case kOval_Type: if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) { return false; } for (int i = 0; i < 4; ++i) { if (!SkScalarNearlyEqual(fRadii[i].fX, SkRectPriv::HalfWidth(fRect)) || !SkScalarNearlyEqual(fRadii[i].fY, SkRectPriv::HalfHeight(fRect))) { return false; } } break; case kSimple_Type: if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) { return false; } break; case kNinePatch_Type: if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare || !patchesOfNine) { return false; } break; case kComplex_Type: if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare || patchesOfNine) { return false; } break; } return true; } bool SkRRect::AreRectAndRadiiValid(const SkRect& rect, const SkVector radii[4]) { if (!rect.isFinite() || !rect.isSorted()) { return false; } for (int i = 0; i < 4; ++i) { if (!are_radius_check_predicates_valid(radii[i].fX, rect.fLeft, rect.fRight) || !are_radius_check_predicates_valid(radii[i].fY, rect.fTop, rect.fBottom)) { return false; } } return true; } /////////////////////////////////////////////////////////////////////////////// SkRect SkRRectPriv::InnerBounds(const SkRRect& rr) { if (rr.isEmpty() || rr.isRect()) { return rr.rect(); } // We start with the outer bounds of the round rect and consider three subsets and take the // one with maximum area. The first two are the horizontal and vertical rects inset from the // corners, the third is the rect inscribed at the corner curves' maximal point. This forms // the exact solution when all corners have the same radii (the radii do not have to be // circular). SkRect innerBounds = rr.getBounds(); SkVector tl = rr.radii(SkRRect::kUpperLeft_Corner); SkVector tr = rr.radii(SkRRect::kUpperRight_Corner); SkVector bl = rr.radii(SkRRect::kLowerLeft_Corner); SkVector br = rr.radii(SkRRect::kLowerRight_Corner); // Select maximum inset per edge, which may move an adjacent corner of the inscribed // rectangle off of the rounded-rect path, but that is acceptable given that the general // equation for inscribed area is non-trivial to evaluate. SkScalar leftShift = std::max(tl.fX, bl.fX); SkScalar topShift = std::max(tl.fY, tr.fY); SkScalar rightShift = std::max(tr.fX, br.fX); SkScalar bottomShift = std::max(bl.fY, br.fY); SkScalar dw = leftShift + rightShift; SkScalar dh = topShift + bottomShift; // Area removed by shifting left/right SkScalar horizArea = (innerBounds.width() - dw) * innerBounds.height(); // And by shifting top/bottom SkScalar vertArea = (innerBounds.height() - dh) * innerBounds.width(); // And by shifting all edges: just considering a corner ellipse, the maximum inscribed rect has // a corner at sqrt(2)/2 * (rX, rY), so scale all corner shifts by (1 - sqrt(2)/2) to get the // safe shift per edge (since the shifts already are the max radius for that edge). // - We actually scale by a value slightly increased to make it so that the shifted corners are // safely inside the curves, otherwise numerical stability can cause it to fail contains(). static constexpr SkScalar kScale = (1.f - SK_ScalarRoot2Over2) + 1e-5f; SkScalar innerArea = (innerBounds.width() - kScale * dw) * (innerBounds.height() - kScale * dh); if (horizArea > vertArea && horizArea > innerArea) { // Cut off corners by insetting left and right innerBounds.fLeft += leftShift; innerBounds.fRight -= rightShift; } else if (vertArea > innerArea) { // Cut off corners by insetting top and bottom innerBounds.fTop += topShift; innerBounds.fBottom -= bottomShift; } else if (innerArea > 0.f) { // Inset on all sides, scaled to touch innerBounds.fLeft += kScale * leftShift; innerBounds.fRight -= kScale * rightShift; innerBounds.fTop += kScale * topShift; innerBounds.fBottom -= kScale * bottomShift; } else { // Inner region would collapse to empty return SkRect::MakeEmpty(); } SkASSERT(innerBounds.isSorted() && !innerBounds.isEmpty()); return innerBounds; } SkRRect SkRRectPriv::ConservativeIntersect(const SkRRect& a, const SkRRect& b) { // Returns the coordinate of the rect matching the corner enum. auto getCorner = [](const SkRect& r, SkRRect::Corner corner) -> SkPoint { switch(corner) { case SkRRect::kUpperLeft_Corner: return {r.fLeft, r.fTop}; case SkRRect::kUpperRight_Corner: return {r.fRight, r.fTop}; case SkRRect::kLowerLeft_Corner: return {r.fLeft, r.fBottom}; case SkRRect::kLowerRight_Corner: return {r.fRight, r.fBottom}; default: SkUNREACHABLE; } }; // Returns true if shape A's extreme point is contained within shape B's extreme point, relative // to the 'corner' location. If the two shapes' corners have the same ellipse radii, this // is sufficient for A's ellipse arc to be contained by B's ellipse arc. auto insideCorner = [](SkRRect::Corner corner, const SkPoint& a, const SkPoint& b) { switch(corner) { case SkRRect::kUpperLeft_Corner: return a.fX >= b.fX && a.fY >= b.fY; case SkRRect::kUpperRight_Corner: return a.fX <= b.fX && a.fY >= b.fY; case SkRRect::kLowerRight_Corner: return a.fX <= b.fX && a.fY <= b.fY; case SkRRect::kLowerLeft_Corner: return a.fX >= b.fX && a.fY <= b.fY; default: SkUNREACHABLE; } }; auto getIntersectionRadii = [&](const SkRect& r, SkRRect::Corner corner, SkVector* radii) { SkPoint test = getCorner(r, corner); SkPoint aCorner = getCorner(a.rect(), corner); SkPoint bCorner = getCorner(b.rect(), corner); if (test == aCorner && test == bCorner) { // The round rects share a corner anchor, so pick A or B such that its X and Y radii // are both larger than the other rrect's, or return false if neither A or B has the max // corner radii (this is more permissive than the single corner tests below). SkVector aRadii = a.radii(corner); SkVector bRadii = b.radii(corner); if (aRadii.fX >= bRadii.fX && aRadii.fY >= bRadii.fY) { *radii = aRadii; return true; } else if (bRadii.fX >= aRadii.fX && bRadii.fY >= aRadii.fY) { *radii = bRadii; return true; } else { return false; } } else if (test == aCorner) { // Test that A's ellipse is contained by B. This is a non-trivial function to evaluate // so we resrict it to when the corners have the same radii. If not, we use the more // conservative test that the extreme point of A's bounding box is contained in B. *radii = a.radii(corner); if (*radii == b.radii(corner)) { return insideCorner(corner, aCorner, bCorner); // A inside B } else { return b.checkCornerContainment(aCorner.fX, aCorner.fY); } } else if (test == bCorner) { // Mirror of the above *radii = b.radii(corner); if (*radii == a.radii(corner)) { return insideCorner(corner, bCorner, aCorner); // B inside A } else { return a.checkCornerContainment(bCorner.fX, bCorner.fY); } } else { // This is a corner formed by two straight edges of A and B, so confirm that it is // contained in both (if not, then the intersection can't be a round rect). *radii = {0.f, 0.f}; return a.checkCornerContainment(test.fX, test.fY) && b.checkCornerContainment(test.fX, test.fY); } }; // We fill in the SkRRect directly. Since the rect and radii are either 0s or determined by // valid existing SkRRects, we know we are finite. SkRRect intersection; if (!intersection.fRect.intersect(a.rect(), b.rect())) { // Definitely no intersection return SkRRect::MakeEmpty(); } const SkRRect::Corner corners[] = { SkRRect::kUpperLeft_Corner, SkRRect::kUpperRight_Corner, SkRRect::kLowerRight_Corner, SkRRect::kLowerLeft_Corner }; // By definition, edges is contained in the bounds of 'a' and 'b', but now we need to consider // the corners. If the bound's corner point is in both rrects, the corner radii will be 0s. // If the bound's corner point matches a's edges and is inside 'b', we use a's radii. // Same for b's radii. If any corner fails these conditions, we reject the intersection as an // rrect. If after determining radii for all 4 corners, they would overlap, we also reject the // intersection shape. for (auto c : corners) { if (!getIntersectionRadii(intersection.fRect, c, &intersection.fRadii[c])) { return SkRRect::MakeEmpty(); // Resulting intersection is not a rrect } } // Check for radius overlap along the four edges, since the earlier evaluation was only a // one-sided corner check. If they aren't valid, a corner's radii doesn't fit within the rect. // If the radii are scaled, the combination of radii from two adjacent corners doesn't fit. // Normally for a regularly constructed SkRRect, we want this scaling, but in this case it means // the intersection shape is definitively not a round rect. if (!SkRRect::AreRectAndRadiiValid(intersection.fRect, intersection.fRadii) || intersection.scaleRadii()) { return SkRRect::MakeEmpty(); } // The intersection is an rrect of the given radii. Potentially all 4 corners could have // been simplified to (0,0) radii, making the intersection a rectangle. intersection.computeType(); return intersection; } /////////////////////////////////////////////////////////////////////////////// bool SkStream::readS8(int8_t* i) { return this->read(i, sizeof(*i)) == sizeof(*i); } bool SkStream::readS16(int16_t* i) { return this->read(i, sizeof(*i)) == sizeof(*i); } bool SkStream::readS32(int32_t* i) { return this->read(i, sizeof(*i)) == sizeof(*i); } bool SkStream::readScalar(SkScalar* i) { return this->read(i, sizeof(*i)) == sizeof(*i); } #define SK_MAX_BYTE_FOR_U8 0xFD #define SK_BYTE_SENTINEL_FOR_U16 0xFE #define SK_BYTE_SENTINEL_FOR_U32 0xFF bool SkStream::readPackedUInt(size_t* i) { uint8_t byte; if (!this->read(&byte, 1)) { return false; } if (SK_BYTE_SENTINEL_FOR_U16 == byte) { uint16_t i16; if (!this->readU16(&i16)) { return false; } *i = i16; } else if (SK_BYTE_SENTINEL_FOR_U32 == byte) { uint32_t i32; if (!this->readU32(&i32)) { return false; } *i = i32; } else { *i = byte; } return true; } ////////////////////////////////////////////////////////////////////////////////////// SkWStream::~SkWStream() { } void SkWStream::flush() { } bool SkWStream::writeDecAsText(int32_t dec) { char buffer[kSkStrAppendS32_MaxSize]; char* stop = SkStrAppendS32(buffer, dec); return this->write(buffer, stop - buffer); } bool SkWStream::writeBigDecAsText(int64_t dec, int minDigits) { char buffer[kSkStrAppendU64_MaxSize]; char* stop = SkStrAppendU64(buffer, dec, minDigits); return this->write(buffer, stop - buffer); } bool SkWStream::writeHexAsText(uint32_t hex, int digits) { SkString tmp; tmp.appendHex(hex, digits); return this->write(tmp.c_str(), tmp.size()); } bool SkWStream::writeScalarAsText(SkScalar value) { char buffer[kSkStrAppendScalar_MaxSize]; char* stop = SkStrAppendScalar(buffer, value); return this->write(buffer, stop - buffer); } bool SkWStream::writeScalar(SkScalar value) { return this->write(&value, sizeof(value)); } int SkWStream::SizeOfPackedUInt(size_t value) { if (value <= SK_MAX_BYTE_FOR_U8) { return 1; } else if (value <= 0xFFFF) { return 3; } return 5; } bool SkWStream::writePackedUInt(size_t value) { uint8_t data[5]; size_t len = 1; if (value <= SK_MAX_BYTE_FOR_U8) { data[0] = value; len = 1; } else if (value <= 0xFFFF) { uint16_t value16 = value; data[0] = SK_BYTE_SENTINEL_FOR_U16; memcpy(&data[1], &value16, 2); len = 3; } else { uint32_t value32 = SkToU32(value); data[0] = SK_BYTE_SENTINEL_FOR_U32; memcpy(&data[1], &value32, 4); len = 5; } return this->write(data, len); } bool SkWStream::writeStream(SkStream* stream, size_t length) { char scratch[1024]; const size_t MAX = sizeof(scratch); while (length != 0) { size_t n = length; if (n > MAX) { n = MAX; } stream->read(scratch, n); if (!this->write(scratch, n)) { return false; } length -= n; } return true; } /////////////////////////////////////////////////////////////////////////////// static sk_sp newFromParams(const void* src, size_t size, bool copyData) { if (copyData) { return SkData::MakeWithCopy(src, size); } else { return SkData::MakeWithoutCopy(src, size); } } SkMemoryStream::SkMemoryStream() { fData = SkData::MakeEmpty(); fOffset = 0; } SkMemoryStream::SkMemoryStream(size_t size) { fData = SkData::MakeUninitialized(size); fOffset = 0; } SkMemoryStream::SkMemoryStream(const void* src, size_t size, bool copyData) { fData = newFromParams(src, size, copyData); fOffset = 0; } SkMemoryStream::SkMemoryStream(sk_sp data) : fData(std::move(data)) { if (nullptr == fData) { fData = SkData::MakeEmpty(); } fOffset = 0; } std::unique_ptr SkMemoryStream::MakeCopy(const void* data, size_t length) { return std::make_unique(data, length, true); } std::unique_ptr SkMemoryStream::MakeDirect(const void* data, size_t length) { return std::make_unique(data, length, false); } std::unique_ptr SkMemoryStream::Make(sk_sp data) { return std::make_unique(std::move(data)); } void SkMemoryStream::setMemoryOwned(const void* src, size_t size) { fData = SkData::MakeFromMalloc(src, size); fOffset = 0; } void SkMemoryStream::setMemory(const void* src, size_t size, bool copyData) { fData = newFromParams(src, size, copyData); fOffset = 0; } void SkMemoryStream::setData(sk_sp data) { if (nullptr == data) { fData = SkData::MakeEmpty(); } else { fData = data; } fOffset = 0; } void SkMemoryStream::skipToAlign4() { // cast to remove unary-minus warning fOffset += -(int)fOffset & 0x03; } size_t SkMemoryStream::read(void* buffer, size_t size) { size_t dataSize = fData->size(); if (size > dataSize - fOffset) { size = dataSize - fOffset; } if (buffer) { memcpy(buffer, fData->bytes() + fOffset, size); } fOffset += size; return size; } size_t SkMemoryStream::peek(void* buffer, size_t size) const { SkASSERT(buffer != nullptr); const size_t currentOffset = fOffset; SkMemoryStream* nonConstThis = const_cast(this); const size_t bytesRead = nonConstThis->read(buffer, size); nonConstThis->fOffset = currentOffset; return bytesRead; } bool SkMemoryStream::isAtEnd() const { return fOffset == fData->size(); } bool SkMemoryStream::rewind() { fOffset = 0; return true; } SkMemoryStream* SkMemoryStream::onDuplicate() const { return new SkMemoryStream(fData); } size_t SkMemoryStream::getPosition() const { return fOffset; } bool SkMemoryStream::seek(size_t position) { fOffset = position > fData->size() ? fData->size() : position; return true; } bool SkMemoryStream::move(long offset) { return this->seek(fOffset + offset); } SkMemoryStream* SkMemoryStream::onFork() const { std::unique_ptr that(this->duplicate()); that->seek(fOffset); return that.release(); } size_t SkMemoryStream::getLength() const { return fData->size(); } const void* SkMemoryStream::getMemoryBase() { return fData->data(); } const void* SkMemoryStream::getAtPos() { return fData->bytes() + fOffset; } ///////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////// static inline void sk_memcpy_4bytes(void* dst, const void* src, size_t size) { if (size == 4) { memcpy(dst, src, 4); } else { memcpy(dst, src, size); } } #define SkDynamicMemoryWStream_MinBlockSize 4096 struct SkDynamicMemoryWStream::Block { Block* fNext; char* fCurr; char* fStop; const char* start() const { return (const char*)(this + 1); } char* start() { return (char*)(this + 1); } size_t avail() const { return fStop - fCurr; } size_t written() const { return fCurr - this->start(); } void init(size_t size) { fNext = nullptr; fCurr = this->start(); fStop = this->start() + size; } const void* append(const void* data, size_t size) { SkASSERT((size_t)(fStop - fCurr) >= size); sk_memcpy_4bytes(fCurr, data, size); fCurr += size; return (const void*)((const char*)data + size); } }; SkDynamicMemoryWStream::SkDynamicMemoryWStream(SkDynamicMemoryWStream&& other) : fHead(other.fHead) , fTail(other.fTail) , fBytesWrittenBeforeTail(other.fBytesWrittenBeforeTail) { other.fHead = nullptr; other.fTail = nullptr; other.fBytesWrittenBeforeTail = 0; } SkDynamicMemoryWStream& SkDynamicMemoryWStream::operator=(SkDynamicMemoryWStream&& other) { if (this != &other) { this->~SkDynamicMemoryWStream(); new (this) SkDynamicMemoryWStream(std::move(other)); } return *this; } SkDynamicMemoryWStream::~SkDynamicMemoryWStream() { this->reset(); } void SkDynamicMemoryWStream::reset() { Block* block = fHead; while (block != nullptr) { Block* next = block->fNext; sk_free(block); block = next; } fHead = fTail = nullptr; fBytesWrittenBeforeTail = 0; } size_t SkDynamicMemoryWStream::bytesWritten() const { this->validate(); if (fTail) { return fBytesWrittenBeforeTail + fTail->written(); } return 0; } bool SkDynamicMemoryWStream::write(const void* buffer, size_t count) { if (count > 0) { SkASSERT(buffer); size_t size; if (fTail) { if (fTail->avail() > 0) { size = std::min(fTail->avail(), count); buffer = fTail->append(buffer, size); SkASSERT(count >= size); count -= size; if (count == 0) { return true; } } // If we get here, we've just exhausted fTail, so update our tracker fBytesWrittenBeforeTail += fTail->written(); } size = std::max(count, SkDynamicMemoryWStream_MinBlockSize - sizeof(Block)); size = SkAlign4(size); // ensure we're always a multiple of 4 (see padToAlign4()) Block* block = (Block*)sk_malloc_throw(sizeof(Block) + size); block->init(size); block->append(buffer, count); if (fTail != nullptr) { fTail->fNext = block; } else { fHead = fTail = block; } fTail = block; this->validate(); } return true; } bool SkDynamicMemoryWStream::writeToAndReset(SkDynamicMemoryWStream* dst) { SkASSERT(dst); SkASSERT(dst != this); if (0 == this->bytesWritten()) { return true; } if (0 == dst->bytesWritten()) { *dst = std::move(*this); return true; } dst->fTail->fNext = fHead; dst->fBytesWrittenBeforeTail += fBytesWrittenBeforeTail + dst->fTail->written(); dst->fTail = fTail; fHead = fTail = nullptr; fBytesWrittenBeforeTail = 0; return true; } void SkDynamicMemoryWStream::prependToAndReset(SkDynamicMemoryWStream* dst) { SkASSERT(dst); SkASSERT(dst != this); if (0 == this->bytesWritten()) { return; } if (0 == dst->bytesWritten()) { *dst = std::move(*this); return; } fTail->fNext = dst->fHead; dst->fHead = fHead; dst->fBytesWrittenBeforeTail += fBytesWrittenBeforeTail + fTail->written(); fHead = fTail = nullptr; fBytesWrittenBeforeTail = 0; return; } bool SkDynamicMemoryWStream::read(void* buffer, size_t offset, size_t count) { if (offset + count > this->bytesWritten()) { return false; // test does not partially modify } Block* block = fHead; while (block != nullptr) { size_t size = block->written(); if (offset < size) { size_t part = offset + count > size ? size - offset : count; memcpy(buffer, block->start() + offset, part); if (count <= part) { return true; } count -= part; buffer = (void*) ((char* ) buffer + part); } offset = offset > size ? offset - size : 0; block = block->fNext; } return false; } void SkDynamicMemoryWStream::copyTo(void* dst) const { SkASSERT(dst); Block* block = fHead; while (block != nullptr) { size_t size = block->written(); memcpy(dst, block->start(), size); dst = (void*)((char*)dst + size); block = block->fNext; } } bool SkDynamicMemoryWStream::writeToStream(SkWStream* dst) const { SkASSERT(dst); for (Block* block = fHead; block != nullptr; block = block->fNext) { if (!dst->write(block->start(), block->written())) { return false; } } return true; } void SkDynamicMemoryWStream::padToAlign4() { // The contract is to write zeros until the entire stream has written a multiple of 4 bytes. // Our Blocks are guaranteed always be (a) full (except the tail) and (b) a multiple of 4 // so it is sufficient to just examine the tail (if present). if (fTail) { // cast to remove unary-minus warning int padBytes = -(int)fTail->written() & 0x03; if (padBytes) { int zero = 0; fTail->append(&zero, padBytes); } } } void SkDynamicMemoryWStream::copyToAndReset(void* ptr) { if (!ptr) { this->reset(); return; } // By looping through the source and freeing as we copy, we // can reduce real memory use with large streams. char* dst = reinterpret_cast(ptr); Block* block = fHead; while (block != nullptr) { size_t len = block->written(); memcpy(dst, block->start(), len); dst += len; Block* next = block->fNext; sk_free(block); block = next; } fHead = fTail = nullptr; fBytesWrittenBeforeTail = 0; } bool SkDynamicMemoryWStream::writeToAndReset(SkWStream* dst) { SkASSERT(dst); // By looping through the source and freeing as we copy, we // can reduce real memory use with large streams. bool dstStreamGood = true; for (Block* block = fHead; block != nullptr; ) { if (dstStreamGood && !dst->write(block->start(), block->written())) { dstStreamGood = false; } Block* next = block->fNext; sk_free(block); block = next; } fHead = fTail = nullptr; fBytesWrittenBeforeTail = 0; return dstStreamGood; } sk_sp SkDynamicMemoryWStream::detachAsData() { const size_t size = this->bytesWritten(); if (0 == size) { return SkData::MakeEmpty(); } sk_sp data = SkData::MakeUninitialized(size); this->copyToAndReset(data->writable_data()); return data; } #ifdef SK_DEBUG void SkDynamicMemoryWStream::validate() const { if (!fHead) { SkASSERT(!fTail); SkASSERT(fBytesWrittenBeforeTail == 0); return; } SkASSERT(fTail); size_t bytes = 0; const Block* block = fHead; while (block) { if (block->fNext) { bytes += block->written(); } block = block->fNext; } SkASSERT(bytes == fBytesWrittenBeforeTail); } #endif //////////////////////////////////////////////////////////////////////////////////////////////// class SkBlockMemoryRefCnt : public SkRefCnt { public: explicit SkBlockMemoryRefCnt(SkDynamicMemoryWStream::Block* head) : fHead(head) { } ~SkBlockMemoryRefCnt() override { SkDynamicMemoryWStream::Block* block = fHead; while (block != nullptr) { SkDynamicMemoryWStream::Block* next = block->fNext; sk_free(block); block = next; } } SkDynamicMemoryWStream::Block* const fHead; }; class SkBlockMemoryStream : public SkStreamAsset { public: SkBlockMemoryStream(sk_sp headRef, size_t size) : fBlockMemory(std::move(headRef)), fCurrent(fBlockMemory->fHead) , fSize(size) , fOffset(0), fCurrentOffset(0) { } size_t read(void* buffer, size_t rawCount) override { size_t count = rawCount; if (fOffset + count > fSize) { count = fSize - fOffset; } size_t bytesLeftToRead = count; while (fCurrent != nullptr) { size_t bytesLeftInCurrent = fCurrent->written() - fCurrentOffset; size_t bytesFromCurrent = std::min(bytesLeftToRead, bytesLeftInCurrent); if (buffer) { memcpy(buffer, fCurrent->start() + fCurrentOffset, bytesFromCurrent); buffer = SkTAddOffset(buffer, bytesFromCurrent); } if (bytesLeftToRead <= bytesFromCurrent) { fCurrentOffset += bytesFromCurrent; fOffset += count; return count; } bytesLeftToRead -= bytesFromCurrent; fCurrent = fCurrent->fNext; fCurrentOffset = 0; } SkASSERT(false); return 0; } bool isAtEnd() const override { return fOffset == fSize; } size_t peek(void* buff, size_t bytesToPeek) const override { SkASSERT(buff != nullptr); bytesToPeek = std::min(bytesToPeek, fSize - fOffset); size_t bytesLeftToPeek = bytesToPeek; char* buffer = static_cast(buff); const SkDynamicMemoryWStream::Block* current = fCurrent; size_t currentOffset = fCurrentOffset; while (bytesLeftToPeek) { SkASSERT(current); size_t bytesFromCurrent = std::min(current->written() - currentOffset, bytesLeftToPeek); memcpy(buffer, current->start() + currentOffset, bytesFromCurrent); bytesLeftToPeek -= bytesFromCurrent; buffer += bytesFromCurrent; current = current->fNext; currentOffset = 0; } return bytesToPeek; } bool rewind() override { fCurrent = fBlockMemory->fHead; fOffset = 0; fCurrentOffset = 0; return true; } SkBlockMemoryStream* onDuplicate() const override { return new SkBlockMemoryStream(fBlockMemory, fSize); } size_t getPosition() const override { return fOffset; } bool seek(size_t position) override { // If possible, skip forward. if (position >= fOffset) { size_t skipAmount = position - fOffset; return this->skip(skipAmount) == skipAmount; } // If possible, move backward within the current block. size_t moveBackAmount = fOffset - position; if (moveBackAmount <= fCurrentOffset) { fCurrentOffset -= moveBackAmount; fOffset -= moveBackAmount; return true; } // Otherwise rewind and move forward. return this->rewind() && this->skip(position) == position; } bool move(long offset) override { return seek(fOffset + offset); } SkBlockMemoryStream* onFork() const override { SkBlockMemoryStream* that = this->onDuplicate(); that->fCurrent = this->fCurrent; that->fOffset = this->fOffset; that->fCurrentOffset = this->fCurrentOffset; return that; } size_t getLength() const override { return fSize; } const void* getMemoryBase() override { if (fBlockMemory->fHead && !fBlockMemory->fHead->fNext) { return fBlockMemory->fHead->start(); } return nullptr; } private: sk_sp const fBlockMemory; SkDynamicMemoryWStream::Block const * fCurrent; size_t const fSize; size_t fOffset; size_t fCurrentOffset; }; std::unique_ptr SkDynamicMemoryWStream::detachAsStream() { if (nullptr == fHead) { // no need to reset. return SkMemoryStream::Make(nullptr); } if (fHead == fTail) { // one block, may be worth shrinking. ptrdiff_t used = fTail->fCurr - (char*)fTail; fHead = fTail = (SkDynamicMemoryWStream::Block*)sk_realloc_throw(fTail, SkToSizeT(used)); fTail->fStop = fTail->fCurr = (char*)fTail + used; // Update pointers. SkASSERT(nullptr == fTail->fNext); SkASSERT(0 == fBytesWrittenBeforeTail); } std::unique_ptr stream = std::make_unique(sk_make_sp(fHead), this->bytesWritten()); fHead = nullptr; // signal reset() to not free anything this->reset(); return stream; } /////////////////////////////////////////////////////////////////////////////// bool SkDebugfStream::write(const void* buffer, size_t size) { SkDebugf("%.*s", (int)size, (const char*)buffer); fBytesWritten += size; return true; } size_t SkDebugfStream::bytesWritten() const { return fBytesWritten; } /////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////// // Declared in SkStreamPriv.h: sk_sp SkCopyStreamToData(SkStream* stream) { SkASSERT(stream != nullptr); if (stream->hasLength()) { return SkData::MakeFromStream(stream, stream->getLength()); } SkDynamicMemoryWStream tempStream; const size_t bufferSize = 4096; char buffer[bufferSize]; do { size_t bytesRead = stream->read(buffer, bufferSize); tempStream.write(buffer, bytesRead); } while (!stream->isAtEnd()); return tempStream.detachAsData(); } bool SkStreamCopy(SkWStream* out, SkStream* input) { const char* base = static_cast(input->getMemoryBase()); if (base && input->hasPosition() && input->hasLength()) { // Shortcut that avoids the while loop. size_t position = input->getPosition(); size_t length = input->getLength(); SkASSERT(length >= position); return out->write(&base[position], length - position); } char scratch[4096]; size_t count; while (true) { count = input->read(scratch, sizeof(scratch)); if (0 == count) { return true; } if (!out->write(scratch, count)) { return false; } } } bool StreamRemainingLengthIsBelow(SkStream* stream, size_t len) { SkASSERT(stream); if (stream->hasLength()) { if (stream->hasPosition()) { size_t remainingBytes = stream->getLength() - stream->getPosition(); return remainingBytes < len; } // We don't know the position, but we can still return true if the // stream's entire length is shorter than the requested length. return stream->getLength() < len; } return false; } // number of bytes (on the stack) to receive the printf result static const size_t kBufferSize = 1024; struct StringBuffer { char* fText; int fLength; }; template static StringBuffer apply_format_string(const char* format, va_list args, char (&stackBuffer)[SIZE], SkString* heapBuffer) SK_PRINTF_LIKE(1, 0); template static StringBuffer apply_format_string(const char* format, va_list args, char (&stackBuffer)[SIZE], SkString* heapBuffer) { // First, attempt to print directly to the stack buffer. va_list argsCopy; va_copy(argsCopy, args); int outLength = std::vsnprintf(stackBuffer, SIZE, format, args); if (outLength < 0) { SkDebugf("SkString: vsnprintf reported error."); va_end(argsCopy); return {stackBuffer, 0}; } if (outLength < SIZE) { va_end(argsCopy); return {stackBuffer, outLength}; } // Our text was too long to fit on the stack! However, we now know how much space we need to // format it. Format the string into our heap buffer. `set` automatically reserves an extra // byte at the end of the buffer for a null terminator, so we don't need to add one here. heapBuffer->set(nullptr, outLength); char* heapBufferDest = heapBuffer->data(); SkDEBUGCODE(int checkLength =) std::vsnprintf(heapBufferDest, outLength + 1, format, argsCopy); SkASSERT(checkLength == outLength); va_end(argsCopy); return {heapBufferDest, outLength}; } /////////////////////////////////////////////////////////////////////////////// bool SkStrEndsWith(const char string[], const char suffixStr[]) { SkASSERT(string); SkASSERT(suffixStr); size_t strLen = strlen(string); size_t suffixLen = strlen(suffixStr); return strLen >= suffixLen && !strncmp(string + strLen - suffixLen, suffixStr, suffixLen); } bool SkStrEndsWith(const char string[], const char suffixChar) { SkASSERT(string); size_t strLen = strlen(string); if (0 == strLen) { return false; } else { return (suffixChar == string[strLen-1]); } } int SkStrStartsWithOneOf(const char string[], const char prefixes[]) { int index = 0; do { const char* limit = strchr(prefixes, '\0'); if (!strncmp(string, prefixes, limit - prefixes)) { return index; } prefixes = limit + 1; index++; } while (prefixes[0]); return -1; } char* SkStrAppendU32(char string[], uint32_t dec) { SkDEBUGCODE(char* start = string;) char buffer[kSkStrAppendU32_MaxSize]; char* p = buffer + sizeof(buffer); do { *--p = SkToU8('0' + dec % 10); dec /= 10; } while (dec != 0); SkASSERT(p >= buffer); size_t cp_len = buffer + sizeof(buffer) - p; memcpy(string, p, cp_len); string += cp_len; SkASSERT(string - start <= kSkStrAppendU32_MaxSize); return string; } char* SkStrAppendS32(char string[], int32_t dec) { uint32_t udec = dec; if (dec < 0) { *string++ = '-'; udec = ~udec + 1; // udec = -udec, but silences some warnings that are trying to be helpful } return SkStrAppendU32(string, udec); } char* SkStrAppendU64(char string[], uint64_t dec, int minDigits) { SkDEBUGCODE(char* start = string;) char buffer[kSkStrAppendU64_MaxSize]; char* p = buffer + sizeof(buffer); do { *--p = SkToU8('0' + (int32_t) (dec % 10)); dec /= 10; minDigits--; } while (dec != 0); while (minDigits > 0) { *--p = '0'; minDigits--; } SkASSERT(p >= buffer); size_t cp_len = buffer + sizeof(buffer) - p; memcpy(string, p, cp_len); string += cp_len; SkASSERT(string - start <= kSkStrAppendU64_MaxSize); return string; } char* SkStrAppendS64(char string[], int64_t dec, int minDigits) { uint64_t udec = dec; if (dec < 0) { *string++ = '-'; udec = ~udec + 1; // udec = -udec, but silences some warnings that are trying to be helpful } return SkStrAppendU64(string, udec, minDigits); } char* SkStrAppendScalar(char string[], SkScalar value) { // Handle infinity and NaN ourselves to ensure consistent cross-platform results. // (e.g.: `inf` versus `1.#INF00`, `nan` versus `-nan` for high-bit-set NaNs) if (SkScalarIsNaN(value)) { strcpy(string, "nan"); return string + 3; } if (!SkScalarIsFinite(value)) { if (value > 0) { strcpy(string, "inf"); return string + 3; } else { strcpy(string, "-inf"); return string + 4; } } // since floats have at most 8 significant digits, we limit our %g to that. static const char gFormat[] = "%.8g"; // make it 1 larger for the terminating 0 char buffer[kSkStrAppendScalar_MaxSize + 1]; int len = snprintf(buffer, sizeof(buffer), gFormat, value); memcpy(string, buffer, len); SkASSERT(len <= kSkStrAppendScalar_MaxSize); return string + len; } /////////////////////////////////////////////////////////////////////////////// const SkString::Rec SkString::gEmptyRec(0, 0); #define SizeOfRec() (gEmptyRec.data() - (const char*)&gEmptyRec) static uint32_t trim_size_t_to_u32(size_t value) { if (sizeof(size_t) > sizeof(uint32_t)) { if (value > UINT32_MAX) { value = UINT32_MAX; } } return (uint32_t)value; } static size_t check_add32(size_t base, size_t extra) { SkASSERT(base <= UINT32_MAX); if (sizeof(size_t) > sizeof(uint32_t)) { if (base + extra > UINT32_MAX) { extra = UINT32_MAX - base; } } return extra; } sk_sp SkString::Rec::Make(const char text[], size_t len) { if (0 == len) { return sk_sp(const_cast(&gEmptyRec)); } SkSafeMath safe; // We store a 32bit version of the length uint32_t stringLen = safe.castTo(len); // Add SizeOfRec() for our overhead and 1 for null-termination size_t allocationSize = safe.add(len, SizeOfRec() + sizeof(char)); // Align up to a multiple of 4 allocationSize = safe.alignUp(allocationSize, 4); SkASSERT_RELEASE(safe.ok()); void* storage = ::operator new (allocationSize); sk_sp rec(new (storage) Rec(stringLen, 1)); if (text) { memcpy(rec->data(), text, len); } rec->data()[len] = 0; return rec; } void SkString::Rec::ref() const { if (this == &SkString::gEmptyRec) { return; } SkAssertResult(this->fRefCnt.fetch_add(+1, std::memory_order_relaxed)); } void SkString::Rec::unref() const { if (this == &SkString::gEmptyRec) { return; } int32_t oldRefCnt = this->fRefCnt.fetch_add(-1, std::memory_order_acq_rel); SkASSERT(oldRefCnt); if (1 == oldRefCnt) { delete this; } } bool SkString::Rec::unique() const { return fRefCnt.load(std::memory_order_acquire) == 1; } #ifdef SK_DEBUG int32_t SkString::Rec::getRefCnt() const { return fRefCnt.load(std::memory_order_relaxed); } const SkString& SkString::validate() const { // make sure no one has written over our global SkASSERT(0 == gEmptyRec.fLength); SkASSERT(0 == gEmptyRec.getRefCnt()); SkASSERT(0 == gEmptyRec.data()[0]); if (fRec.get() != &gEmptyRec) { SkASSERT(fRec->fLength > 0); SkASSERT(fRec->getRefCnt() > 0); SkASSERT(0 == fRec->data()[fRec->fLength]); } return *this; } SkString& SkString::validate() { const_cast(this)->validate(); return *this; } #endif /////////////////////////////////////////////////////////////////////////////// SkString::SkString() : fRec(const_cast(&gEmptyRec)) { } SkString::SkString(size_t len) { fRec = Rec::Make(nullptr, len); } SkString::SkString(const char text[]) { size_t len = text ? strlen(text) : 0; fRec = Rec::Make(text, len); } SkString::SkString(const char text[], size_t len) { fRec = Rec::Make(text, len); } SkString::SkString(const SkString& src) : fRec(src.validate().fRec) {} SkString::SkString(SkString&& src) : fRec(std::move(src.validate().fRec)) { src.fRec.reset(const_cast(&gEmptyRec)); } SkString::SkString(const std::string& src) { fRec = Rec::Make(src.c_str(), src.size()); } SkString::SkString(std::string_view src) { fRec = Rec::Make(src.data(), src.length()); } SkString::~SkString() { this->validate(); } bool SkString::equals(const SkString& src) const { return fRec == src.fRec || this->equals(src.c_str(), src.size()); } bool SkString::equals(const char text[]) const { return this->equals(text, text ? strlen(text) : 0); } bool SkString::equals(const char text[], size_t len) const { SkASSERT(len == 0 || text != nullptr); return fRec->fLength == len && !sk_careful_memcmp(fRec->data(), text, len); } SkString& SkString::operator=(const SkString& src) { this->validate(); fRec = src.fRec; // sk_sp::operator=(const sk_sp&) checks for self-assignment. return *this; } SkString& SkString::operator=(SkString&& src) { this->validate(); if (fRec != src.fRec) { this->swap(src); } return *this; } SkString& SkString::operator=(const char text[]) { this->validate(); return *this = SkString(text); } void SkString::reset() { this->validate(); fRec.reset(const_cast(&gEmptyRec)); } char* SkString::data() { this->validate(); if (fRec->fLength) { if (!fRec->unique()) { fRec = Rec::Make(fRec->data(), fRec->fLength); } } return fRec->data(); } void SkString::resize(size_t len) { len = trim_size_t_to_u32(len); if (0 == len) { this->reset(); } else if (fRec->unique() && ((len >> 2) <= (fRec->fLength >> 2))) { // Use less of the buffer we have without allocating a smaller one. char* p = this->data(); p[len] = '\0'; fRec->fLength = SkToU32(len); } else { SkString newString(len); char* dest = newString.data(); int copyLen = std::min(len, this->size()); memcpy(dest, this->c_str(), copyLen); dest[copyLen] = '\0'; this->swap(newString); } } void SkString::set(const char text[]) { this->set(text, text ? strlen(text) : 0); } void SkString::set(const char text[], size_t len) { len = trim_size_t_to_u32(len); if (0 == len) { this->reset(); } else if (fRec->unique() && ((len >> 2) <= (fRec->fLength >> 2))) { // Use less of the buffer we have without allocating a smaller one. char* p = this->data(); if (text) { memcpy(p, text, len); } p[len] = '\0'; fRec->fLength = SkToU32(len); } else { SkString tmp(text, len); this->swap(tmp); } } void SkString::insert(size_t offset, const char text[]) { this->insert(offset, text, text ? strlen(text) : 0); } void SkString::insert(size_t offset, const char text[], size_t len) { if (len) { size_t length = fRec->fLength; if (offset > length) { offset = length; } // Check if length + len exceeds 32bits, we trim len len = check_add32(length, len); if (0 == len) { return; } /* If we're the only owner, and we have room in our allocation for the insert, do it in place, rather than allocating a new buffer. To know we have room, compare the allocated sizes beforeAlloc = SkAlign4(length + 1) afterAlloc = SkAligh4(length + 1 + len) but SkAlign4(x) is (x + 3) >> 2 << 2 which is equivalent for testing to (length + 1 + 3) >> 2 == (length + 1 + 3 + len) >> 2 and we can then eliminate the +1+3 since that doesn't affec the answer */ if (fRec->unique() && (length >> 2) == ((length + len) >> 2)) { char* dst = this->data(); if (offset < length) { memmove(dst + offset + len, dst + offset, length - offset); } memcpy(dst + offset, text, len); dst[length + len] = 0; fRec->fLength = SkToU32(length + len); } else { /* Seems we should use realloc here, since that is safe if it fails (we have the original data), and might be faster than alloc/copy/free. */ SkString tmp(fRec->fLength + len); char* dst = tmp.data(); if (offset > 0) { memcpy(dst, fRec->data(), offset); } memcpy(dst + offset, text, len); if (offset < fRec->fLength) { memcpy(dst + offset + len, fRec->data() + offset, fRec->fLength - offset); } this->swap(tmp); } } } void SkString::insertUnichar(size_t offset, SkUnichar uni) { char buffer[SkUTF::kMaxBytesInUTF8Sequence]; size_t len = SkUTF::ToUTF8(uni, buffer); if (len) { this->insert(offset, buffer, len); } } void SkString::insertS32(size_t offset, int32_t dec) { char buffer[kSkStrAppendS32_MaxSize]; char* stop = SkStrAppendS32(buffer, dec); this->insert(offset, buffer, stop - buffer); } void SkString::insertS64(size_t offset, int64_t dec, int minDigits) { char buffer[kSkStrAppendS64_MaxSize]; char* stop = SkStrAppendS64(buffer, dec, minDigits); this->insert(offset, buffer, stop - buffer); } void SkString::insertU32(size_t offset, uint32_t dec) { char buffer[kSkStrAppendU32_MaxSize]; char* stop = SkStrAppendU32(buffer, dec); this->insert(offset, buffer, stop - buffer); } void SkString::insertU64(size_t offset, uint64_t dec, int minDigits) { char buffer[kSkStrAppendU64_MaxSize]; char* stop = SkStrAppendU64(buffer, dec, minDigits); this->insert(offset, buffer, stop - buffer); } void SkString::insertHex(size_t offset, uint32_t hex, int minDigits) { minDigits = SkTPin(minDigits, 0, 8); char buffer[8]; char* p = buffer + sizeof(buffer); do { *--p = SkHexadecimalDigits::gUpper[hex & 0xF]; hex >>= 4; minDigits -= 1; } while (hex != 0); while (--minDigits >= 0) { *--p = '0'; } SkASSERT(p >= buffer); this->insert(offset, p, buffer + sizeof(buffer) - p); } void SkString::insertScalar(size_t offset, SkScalar value) { char buffer[kSkStrAppendScalar_MaxSize]; char* stop = SkStrAppendScalar(buffer, value); this->insert(offset, buffer, stop - buffer); } /////////////////////////////////////////////////////////////////////////////// void SkString::printf(const char format[], ...) { va_list args; va_start(args, format); this->printVAList(format, args); va_end(args); } void SkString::printVAList(const char format[], va_list args) { char stackBuffer[kBufferSize]; StringBuffer result = apply_format_string(format, args, stackBuffer, this); if (result.fText == stackBuffer) { this->set(result.fText, result.fLength); } } void SkString::appendf(const char format[], ...) { va_list args; va_start(args, format); this->appendVAList(format, args); va_end(args); } void SkString::appendVAList(const char format[], va_list args) { if (this->isEmpty()) { this->printVAList(format, args); return; } SkString overflow; char stackBuffer[kBufferSize]; StringBuffer result = apply_format_string(format, args, stackBuffer, &overflow); this->append(result.fText, result.fLength); } void SkString::prependf(const char format[], ...) { va_list args; va_start(args, format); this->prependVAList(format, args); va_end(args); } void SkString::prependVAList(const char format[], va_list args) { if (this->isEmpty()) { this->printVAList(format, args); return; } SkString overflow; char stackBuffer[kBufferSize]; StringBuffer result = apply_format_string(format, args, stackBuffer, &overflow); this->prepend(result.fText, result.fLength); } /////////////////////////////////////////////////////////////////////////////// void SkString::remove(size_t offset, size_t length) { size_t size = this->size(); if (offset < size) { if (length > size - offset) { length = size - offset; } SkASSERT(length <= size); SkASSERT(offset <= size - length); if (length > 0) { SkString tmp(size - length); char* dst = tmp.data(); const char* src = this->c_str(); if (offset) { memcpy(dst, src, offset); } size_t tail = size - (offset + length); if (tail) { memcpy(dst + offset, src + (offset + length), tail); } SkASSERT(dst[tmp.size()] == 0); this->swap(tmp); } } } void SkString::swap(SkString& other) { this->validate(); other.validate(); using std::swap; swap(fRec, other.fRec); } /////////////////////////////////////////////////////////////////////////////// SkString SkStringPrintf(const char* format, ...) { SkString formattedOutput; va_list args; va_start(args, format); formattedOutput.printVAList(format, args); va_end(args); return formattedOutput; } using namespace skia_private; void SkAppendScalar(SkString* str, SkScalar value, SkScalarAsStringType asType) { switch (asType) { case kHex_SkScalarAsStringType: str->appendf("SkBits2Float(0x%08x)", SkFloat2Bits(value)); break; case kDec_SkScalarAsStringType: { SkString tmp; tmp.printf("%.9g", value); if (tmp.contains('.')) { tmp.appendUnichar('f'); } str->append(tmp); break; } } } SkString SkTabString(const SkString& string, int tabCnt) { if (tabCnt <= 0) { return string; } SkString tabs; for (int i = 0; i < tabCnt; ++i) { tabs.append("\t"); } SkString result; static const char newline[] = "\n"; const char* input = string.c_str(); int nextNL = SkStrFind(input, newline); while (nextNL >= 0) { if (nextNL > 0) { result.append(tabs); } result.append(input, nextNL + 1); input += nextNL + 1; nextNL = SkStrFind(input, newline); } if (*input != '\0') { result.append(tabs); result.append(input); } return result; } SkString SkStringFromUTF16(const uint16_t* src, size_t count) { SkString ret; const uint16_t* stop = src + count; if (count > 0) { SkASSERT(src); size_t n = 0; const uint16_t* end = src + count; for (const uint16_t* ptr = src; ptr < end;) { const uint16_t* last = ptr; SkUnichar u = SkUTF::NextUTF16(&ptr, stop); size_t s = SkUTF::ToUTF8(u); if (n > UINT32_MAX - s) { end = last; // truncate input string break; } n += s; } ret = SkString(n); char* out = ret.data(); for (const uint16_t* ptr = src; ptr < end;) { out += SkUTF::ToUTF8(SkUTF::NextUTF16(&ptr, stop), out); } SkASSERT(out == ret.data() + n); } return ret; } void SkStrSplit(const char* str, const char* delimiters, SkStrSplitMode splitMode, TArray* out) { if (splitMode == kCoalesce_SkStrSplitMode) { // Skip any delimiters. str += strspn(str, delimiters); } if (!*str) { return; } while (true) { // Find a token. const size_t len = strcspn(str, delimiters); if (splitMode == kStrict_SkStrSplitMode || len > 0) { out->push_back().set(str, len); str += len; } if (!*str) { return; } if (splitMode == kCoalesce_SkStrSplitMode) { // Skip any delimiters. str += strspn(str, delimiters); } else { // Skip one delimiter. str += 1; } } } #if DEBUG_ADD_INTERSECTING_TS static void debugShowLineIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " LINE_DEBUG_STR " " LINE_DEBUG_STR "\n", __FUNCTION__, LINE_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " LINE_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], LINE_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); if (pts == 2) { SkDebugf(" " T_DEBUG_STR(wtTs, 1) " " PT_DEBUG_STR, i[0][1], PT_DEBUG_DATA(i, 1)); } SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); if (pts == 2) { SkDebugf(" " T_DEBUG_STR(wnTs, 1), i[1][1]); } SkDebugf("\n"); } static void debugShowQuadLineIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " QUAD_DEBUG_STR " " LINE_DEBUG_STR "\n", __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowQuadIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " QUAD_DEBUG_STR " " QUAD_DEBUG_STR "\n", __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowConicLineIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " CONIC_DEBUG_STR " " LINE_DEBUG_STR "\n", __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), LINE_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowConicQuadIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " CONIC_DEBUG_STR " " QUAD_DEBUG_STR "\n", __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), QUAD_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowConicIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " CONIC_DEBUG_STR " " CONIC_DEBUG_STR "\n", __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), CONIC_DEBUG_DATA(wn.pts(), wn.weight())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " CONIC_DEBUG_STR, i[1][0], CONIC_DEBUG_DATA(wn.pts(), wn.weight())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowCubicLineIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " LINE_DEBUG_STR "\n", __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowCubicQuadIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " QUAD_DEBUG_STR "\n", __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowCubicConicIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CONIC_DEBUG_STR "\n", __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CONIC_DEBUG_DATA(wn.pts(), wn.weight())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " CONIC_DEBUG_STR, i[1][0], CONIC_DEBUG_DATA(wn.pts(), wn.weight())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn, const SkIntersections& i) { SkASSERT(i.used() == pts); if (!pts) { SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CUBIC_DEBUG_STR "\n", __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CUBIC_DEBUG_DATA(wn.pts())); return; } SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); } SkDebugf(" wnTs[0]=%g " CUBIC_DEBUG_STR, i[1][0], CUBIC_DEBUG_DATA(wn.pts())); for (int n = 1; n < pts; ++n) { SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); } SkDebugf("\n"); } #else static void debugShowLineIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowQuadLineIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowQuadIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowConicLineIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowConicQuadIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowConicIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowCubicLineIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowCubicQuadIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowCubicConicIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } static void debugShowCubicIntersection(int , const SkIntersectionHelper& , const SkIntersectionHelper& , const SkIntersections& ) { } #endif bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence) { if (test != next) { if (AlmostLessUlps(test->bounds().fBottom, next->bounds().fTop)) { return false; } // OPTIMIZATION: outset contour bounds a smidgen instead? if (!SkPathOpsBounds::Intersects(test->bounds(), next->bounds())) { return true; } } SkIntersectionHelper wt; wt.init(test); do { SkIntersectionHelper wn; wn.init(next); test->debugValidate(); next->debugValidate(); if (test == next && !wn.startAfter(wt)) { continue; } do { if (!SkPathOpsBounds::Intersects(wt.bounds(), wn.bounds())) { continue; } int pts = 0; SkIntersections ts { SkDEBUGCODE(test->globalState()) }; bool swap = false; SkDQuad quad1, quad2; SkDConic conic1, conic2; SkDCubic cubic1, cubic2; switch (wt.segmentType()) { case SkIntersectionHelper::kHorizontalLine_Segment: swap = true; switch (wn.segmentType()) { case SkIntersectionHelper::kHorizontalLine_Segment: case SkIntersectionHelper::kVerticalLine_Segment: case SkIntersectionHelper::kLine_Segment: pts = ts.lineHorizontal(wn.pts(), wt.left(), wt.right(), wt.y(), wt.xFlipped()); debugShowLineIntersection(pts, wn, wt, ts); break; case SkIntersectionHelper::kQuad_Segment: pts = ts.quadHorizontal(wn.pts(), wt.left(), wt.right(), wt.y(), wt.xFlipped()); debugShowQuadLineIntersection(pts, wn, wt, ts); break; case SkIntersectionHelper::kConic_Segment: pts = ts.conicHorizontal(wn.pts(), wn.weight(), wt.left(), wt.right(), wt.y(), wt.xFlipped()); debugShowConicLineIntersection(pts, wn, wt, ts); break; case SkIntersectionHelper::kCubic_Segment: pts = ts.cubicHorizontal(wn.pts(), wt.left(), wt.right(), wt.y(), wt.xFlipped()); debugShowCubicLineIntersection(pts, wn, wt, ts); break; default: SkASSERT(0); } break; case SkIntersectionHelper::kVerticalLine_Segment: swap = true; switch (wn.segmentType()) { case SkIntersectionHelper::kHorizontalLine_Segment: case SkIntersectionHelper::kVerticalLine_Segment: case SkIntersectionHelper::kLine_Segment: { pts = ts.lineVertical(wn.pts(), wt.top(), wt.bottom(), wt.x(), wt.yFlipped()); debugShowLineIntersection(pts, wn, wt, ts); break; } case SkIntersectionHelper::kQuad_Segment: { pts = ts.quadVertical(wn.pts(), wt.top(), wt.bottom(), wt.x(), wt.yFlipped()); debugShowQuadLineIntersection(pts, wn, wt, ts); break; } case SkIntersectionHelper::kConic_Segment: { pts = ts.conicVertical(wn.pts(), wn.weight(), wt.top(), wt.bottom(), wt.x(), wt.yFlipped()); debugShowConicLineIntersection(pts, wn, wt, ts); break; } case SkIntersectionHelper::kCubic_Segment: { pts = ts.cubicVertical(wn.pts(), wt.top(), wt.bottom(), wt.x(), wt.yFlipped()); debugShowCubicLineIntersection(pts, wn, wt, ts); break; } default: SkASSERT(0); } break; case SkIntersectionHelper::kLine_Segment: switch (wn.segmentType()) { case SkIntersectionHelper::kHorizontalLine_Segment: pts = ts.lineHorizontal(wt.pts(), wn.left(), wn.right(), wn.y(), wn.xFlipped()); debugShowLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kVerticalLine_Segment: pts = ts.lineVertical(wt.pts(), wn.top(), wn.bottom(), wn.x(), wn.yFlipped()); debugShowLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kLine_Segment: pts = ts.lineLine(wt.pts(), wn.pts()); debugShowLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kQuad_Segment: swap = true; pts = ts.quadLine(wn.pts(), wt.pts()); debugShowQuadLineIntersection(pts, wn, wt, ts); break; case SkIntersectionHelper::kConic_Segment: swap = true; pts = ts.conicLine(wn.pts(), wn.weight(), wt.pts()); debugShowConicLineIntersection(pts, wn, wt, ts); break; case SkIntersectionHelper::kCubic_Segment: swap = true; pts = ts.cubicLine(wn.pts(), wt.pts()); debugShowCubicLineIntersection(pts, wn, wt, ts); break; default: SkASSERT(0); } break; case SkIntersectionHelper::kQuad_Segment: switch (wn.segmentType()) { case SkIntersectionHelper::kHorizontalLine_Segment: pts = ts.quadHorizontal(wt.pts(), wn.left(), wn.right(), wn.y(), wn.xFlipped()); debugShowQuadLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kVerticalLine_Segment: pts = ts.quadVertical(wt.pts(), wn.top(), wn.bottom(), wn.x(), wn.yFlipped()); debugShowQuadLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kLine_Segment: pts = ts.quadLine(wt.pts(), wn.pts()); debugShowQuadLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kQuad_Segment: { pts = ts.intersect(quad1.set(wt.pts()), quad2.set(wn.pts())); debugShowQuadIntersection(pts, wt, wn, ts); break; } case SkIntersectionHelper::kConic_Segment: { swap = true; pts = ts.intersect(conic2.set(wn.pts(), wn.weight()), quad1.set(wt.pts())); debugShowConicQuadIntersection(pts, wn, wt, ts); break; } case SkIntersectionHelper::kCubic_Segment: { swap = true; pts = ts.intersect(cubic2.set(wn.pts()), quad1.set(wt.pts())); debugShowCubicQuadIntersection(pts, wn, wt, ts); break; } default: SkASSERT(0); } break; case SkIntersectionHelper::kConic_Segment: switch (wn.segmentType()) { case SkIntersectionHelper::kHorizontalLine_Segment: pts = ts.conicHorizontal(wt.pts(), wt.weight(), wn.left(), wn.right(), wn.y(), wn.xFlipped()); debugShowConicLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kVerticalLine_Segment: pts = ts.conicVertical(wt.pts(), wt.weight(), wn.top(), wn.bottom(), wn.x(), wn.yFlipped()); debugShowConicLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kLine_Segment: pts = ts.conicLine(wt.pts(), wt.weight(), wn.pts()); debugShowConicLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kQuad_Segment: { pts = ts.intersect(conic1.set(wt.pts(), wt.weight()), quad2.set(wn.pts())); debugShowConicQuadIntersection(pts, wt, wn, ts); break; } case SkIntersectionHelper::kConic_Segment: { pts = ts.intersect(conic1.set(wt.pts(), wt.weight()), conic2.set(wn.pts(), wn.weight())); debugShowConicIntersection(pts, wt, wn, ts); break; } case SkIntersectionHelper::kCubic_Segment: { swap = true; pts = ts.intersect(cubic2.set(wn.pts() SkDEBUGPARAMS(ts.globalState())), conic1.set(wt.pts(), wt.weight() SkDEBUGPARAMS(ts.globalState()))); debugShowCubicConicIntersection(pts, wn, wt, ts); break; } } break; case SkIntersectionHelper::kCubic_Segment: switch (wn.segmentType()) { case SkIntersectionHelper::kHorizontalLine_Segment: pts = ts.cubicHorizontal(wt.pts(), wn.left(), wn.right(), wn.y(), wn.xFlipped()); debugShowCubicLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kVerticalLine_Segment: pts = ts.cubicVertical(wt.pts(), wn.top(), wn.bottom(), wn.x(), wn.yFlipped()); debugShowCubicLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kLine_Segment: pts = ts.cubicLine(wt.pts(), wn.pts()); debugShowCubicLineIntersection(pts, wt, wn, ts); break; case SkIntersectionHelper::kQuad_Segment: { pts = ts.intersect(cubic1.set(wt.pts()), quad2.set(wn.pts())); debugShowCubicQuadIntersection(pts, wt, wn, ts); break; } case SkIntersectionHelper::kConic_Segment: { pts = ts.intersect(cubic1.set(wt.pts() SkDEBUGPARAMS(ts.globalState())), conic2.set(wn.pts(), wn.weight() SkDEBUGPARAMS(ts.globalState()))); debugShowCubicConicIntersection(pts, wt, wn, ts); break; } case SkIntersectionHelper::kCubic_Segment: { pts = ts.intersect(cubic1.set(wt.pts()), cubic2.set(wn.pts())); debugShowCubicIntersection(pts, wt, wn, ts); break; } default: SkASSERT(0); } break; default: SkASSERT(0); } #if DEBUG_T_SECT_LOOP_COUNT test->globalState()->debugAddLoopCount(&ts, wt, wn); #endif int coinIndex = -1; SkOpPtT* coinPtT[2]; for (int pt = 0; pt < pts; ++pt) { SkASSERT(ts[0][pt] >= 0 && ts[0][pt] <= 1); SkASSERT(ts[1][pt] >= 0 && ts[1][pt] <= 1); wt.segment()->debugValidate(); // if t value is used to compute pt in addT, error may creep in and // rect intersections may result in non-rects. if pt value from intersection // is passed in, current tests break. As a workaround, pass in pt // value from intersection only if pt.x and pt.y is integral SkPoint iPt = ts.pt(pt).asSkPoint(); bool iPtIsIntegral = iPt.fX == floor(iPt.fX) && iPt.fY == floor(iPt.fY); SkOpPtT* testTAt = iPtIsIntegral ? wt.segment()->addT(ts[swap][pt], iPt) : wt.segment()->addT(ts[swap][pt]); wn.segment()->debugValidate(); SkOpPtT* nextTAt = iPtIsIntegral ? wn.segment()->addT(ts[!swap][pt], iPt) : wn.segment()->addT(ts[!swap][pt]); if (!testTAt->contains(nextTAt)) { SkOpPtT* oppPrev = testTAt->oppPrev(nextTAt); // Returns nullptr if pair if (oppPrev) { // already share a pt-t loop. testTAt->span()->mergeMatches(nextTAt->span()); testTAt->addOpp(nextTAt, oppPrev); } if (testTAt->fPt != nextTAt->fPt) { testTAt->span()->unaligned(); nextTAt->span()->unaligned(); } wt.segment()->debugValidate(); wn.segment()->debugValidate(); } if (!ts.isCoincident(pt)) { continue; } if (coinIndex < 0) { coinPtT[0] = testTAt; coinPtT[1] = nextTAt; coinIndex = pt; continue; } if (coinPtT[0]->span() == testTAt->span()) { coinIndex = -1; continue; } if (coinPtT[1]->span() == nextTAt->span()) { coinIndex = -1; // coincidence span collapsed continue; } if (swap) { using std::swap; swap(coinPtT[0], coinPtT[1]); swap(testTAt, nextTAt); } SkASSERT(coincidence->globalState()->debugSkipAssert() || coinPtT[0]->span()->t() < testTAt->span()->t()); if (coinPtT[0]->span()->deleted()) { coinIndex = -1; continue; } if (testTAt->span()->deleted()) { coinIndex = -1; continue; } coincidence->add(coinPtT[0], testTAt, coinPtT[1], nextTAt); wt.segment()->debugValidate(); wn.segment()->debugValidate(); coinIndex = -1; } SkOPOBJASSERT(coincidence, coinIndex < 0); // expect coincidence to be paired } while (wn.advance()); } while (wt.advance()); return true; } class LineConicIntersections { public: enum PinTPoint { kPointUninitialized, kPointInitialized }; LineConicIntersections(const SkDConic& c, const SkDLine& l, SkIntersections* i) : fConic(c) , fLine(&l) , fIntersections(i) , fAllowNear(true) { i->setMax(4); // allow short partial coincidence plus discrete intersection } LineConicIntersections(const SkDConic& c) : fConic(c) SkDEBUGPARAMS(fLine(nullptr)) SkDEBUGPARAMS(fIntersections(nullptr)) SkDEBUGPARAMS(fAllowNear(false)) { } void allowNear(bool allow) { fAllowNear = allow; } void checkCoincident() { int last = fIntersections->used() - 1; for (int index = 0; index < last; ) { double conicMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; SkDPoint conicMidPt = fConic.ptAtT(conicMidT); double t = fLine->nearPoint(conicMidPt, nullptr); if (t < 0) { ++index; continue; } if (fIntersections->isCoincident(index)) { fIntersections->removeOne(index); --last; } else if (fIntersections->isCoincident(index + 1)) { fIntersections->removeOne(index + 1); --last; } else { fIntersections->setCoincident(index++); } fIntersections->setCoincident(index); } } #ifdef SK_DEBUG static bool close_to(double a, double b, const double c[3]) { double max = std::max(-std::min(std::min(c[0], c[1]), c[2]), std::max(std::max(c[0], c[1]), c[2])); return approximately_zero_when_compared_to(a - b, max); } #endif int horizontalIntersect(double axisIntercept, double roots[2]) { double conicVals[] = { fConic[0].fY, fConic[1].fY, fConic[2].fY }; return this->validT(conicVals, axisIntercept, roots); } int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { this->addExactHorizontalEndPoints(left, right, axisIntercept); if (fAllowNear) { this->addNearHorizontalEndPoints(left, right, axisIntercept); } double roots[2]; int count = this->horizontalIntersect(axisIntercept, roots); for (int index = 0; index < count; ++index) { double conicT = roots[index]; SkDPoint pt = fConic.ptAtT(conicT); SkDEBUGCODE(double conicVals[] = { fConic[0].fY, fConic[1].fY, fConic[2].fY }); SkOPOBJASSERT(fIntersections, close_to(pt.fY, axisIntercept, conicVals)); double lineT = (pt.fX - left) / (right - left); if (this->pinTs(&conicT, &lineT, &pt, kPointInitialized) && this->uniqueAnswer(conicT, pt)) { fIntersections->insert(conicT, lineT, pt); } } if (flipped) { fIntersections->flip(); } this->checkCoincident(); return fIntersections->used(); } int intersect() { this->addExactEndPoints(); if (fAllowNear) { this->addNearEndPoints(); } double rootVals[2]; int roots = this->intersectRay(rootVals); for (int index = 0; index < roots; ++index) { double conicT = rootVals[index]; double lineT = this->findLineT(conicT); #ifdef SK_DEBUG if (!fIntersections->globalState() || !fIntersections->globalState()->debugSkipAssert()) { SkDEBUGCODE(SkDPoint conicPt = fConic.ptAtT(conicT)); SkDEBUGCODE(SkDPoint linePt = fLine->ptAtT(lineT)); SkASSERT(conicPt.approximatelyDEqual(linePt)); } #endif SkDPoint pt; if (this->pinTs(&conicT, &lineT, &pt, kPointUninitialized) && this->uniqueAnswer(conicT, pt)) { fIntersections->insert(conicT, lineT, pt); } } this->checkCoincident(); return fIntersections->used(); } int intersectRay(double roots[2]) { double adj = (*fLine)[1].fX - (*fLine)[0].fX; double opp = (*fLine)[1].fY - (*fLine)[0].fY; double r[3]; for (int n = 0; n < 3; ++n) { r[n] = (fConic[n].fY - (*fLine)[0].fY) * adj - (fConic[n].fX - (*fLine)[0].fX) * opp; } return this->validT(r, 0, roots); } int validT(double r[3], double axisIntercept, double roots[2]) { double A = r[2]; double B = r[1] * fConic.fWeight - axisIntercept * fConic.fWeight + axisIntercept; double C = r[0]; A += C - 2 * B; // A = a + c - 2*(b*w - xCept*w + xCept) B -= C; // B = b*w - w * xCept + xCept - a C -= axisIntercept; return SkDQuad::RootsValidT(A, 2 * B, C, roots); } int verticalIntersect(double axisIntercept, double roots[2]) { double conicVals[] = { fConic[0].fX, fConic[1].fX, fConic[2].fX }; return this->validT(conicVals, axisIntercept, roots); } int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { this->addExactVerticalEndPoints(top, bottom, axisIntercept); if (fAllowNear) { this->addNearVerticalEndPoints(top, bottom, axisIntercept); } double roots[2]; int count = this->verticalIntersect(axisIntercept, roots); for (int index = 0; index < count; ++index) { double conicT = roots[index]; SkDPoint pt = fConic.ptAtT(conicT); SkDEBUGCODE(double conicVals[] = { fConic[0].fX, fConic[1].fX, fConic[2].fX }); SkOPOBJASSERT(fIntersections, close_to(pt.fX, axisIntercept, conicVals)); double lineT = (pt.fY - top) / (bottom - top); if (this->pinTs(&conicT, &lineT, &pt, kPointInitialized) && this->uniqueAnswer(conicT, pt)) { fIntersections->insert(conicT, lineT, pt); } } if (flipped) { fIntersections->flip(); } this->checkCoincident(); return fIntersections->used(); } protected: // OPTIMIZE: Functions of the form add .. points are indentical to the conic routines. // add endpoints first to get zero and one t values exactly void addExactEndPoints() { for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { double lineT = fLine->exactPoint(fConic[cIndex]); if (lineT < 0) { continue; } double conicT = (double) (cIndex >> 1); fIntersections->insert(conicT, lineT, fConic[cIndex]); } } void addNearEndPoints() { for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { double conicT = (double) (cIndex >> 1); if (fIntersections->hasT(conicT)) { continue; } double lineT = fLine->nearPoint(fConic[cIndex], nullptr); if (lineT < 0) { continue; } fIntersections->insert(conicT, lineT, fConic[cIndex]); } this->addLineNearEndPoints(); } void addLineNearEndPoints() { for (int lIndex = 0; lIndex < 2; ++lIndex) { double lineT = (double) lIndex; if (fIntersections->hasOppT(lineT)) { continue; } double conicT = ((SkDCurve*) &fConic)->nearPoint(SkPath::kConic_Verb, (*fLine)[lIndex], (*fLine)[!lIndex]); if (conicT < 0) { continue; } fIntersections->insert(conicT, lineT, (*fLine)[lIndex]); } } void addExactHorizontalEndPoints(double left, double right, double y) { for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { double lineT = SkDLine::ExactPointH(fConic[cIndex], left, right, y); if (lineT < 0) { continue; } double conicT = (double) (cIndex >> 1); fIntersections->insert(conicT, lineT, fConic[cIndex]); } } void addNearHorizontalEndPoints(double left, double right, double y) { for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { double conicT = (double) (cIndex >> 1); if (fIntersections->hasT(conicT)) { continue; } double lineT = SkDLine::NearPointH(fConic[cIndex], left, right, y); if (lineT < 0) { continue; } fIntersections->insert(conicT, lineT, fConic[cIndex]); } this->addLineNearEndPoints(); } void addExactVerticalEndPoints(double top, double bottom, double x) { for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { double lineT = SkDLine::ExactPointV(fConic[cIndex], top, bottom, x); if (lineT < 0) { continue; } double conicT = (double) (cIndex >> 1); fIntersections->insert(conicT, lineT, fConic[cIndex]); } } void addNearVerticalEndPoints(double top, double bottom, double x) { for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { double conicT = (double) (cIndex >> 1); if (fIntersections->hasT(conicT)) { continue; } double lineT = SkDLine::NearPointV(fConic[cIndex], top, bottom, x); if (lineT < 0) { continue; } fIntersections->insert(conicT, lineT, fConic[cIndex]); } this->addLineNearEndPoints(); } double findLineT(double t) { SkDPoint xy = fConic.ptAtT(t); double dx = (*fLine)[1].fX - (*fLine)[0].fX; double dy = (*fLine)[1].fY - (*fLine)[0].fY; if (fabs(dx) > fabs(dy)) { return (xy.fX - (*fLine)[0].fX) / dx; } return (xy.fY - (*fLine)[0].fY) / dy; } bool pinTs(double* conicT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { if (!approximately_one_or_less_double(*lineT)) { return false; } if (!approximately_zero_or_more_double(*lineT)) { return false; } double qT = *conicT = SkPinT(*conicT); double lT = *lineT = SkPinT(*lineT); if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && qT != 0 && qT != 1)) { *pt = (*fLine).ptAtT(lT); } else if (ptSet == kPointUninitialized) { *pt = fConic.ptAtT(qT); } SkPoint gridPt = pt->asSkPoint(); if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[0].asSkPoint())) { *pt = (*fLine)[0]; *lineT = 0; } else if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[1].asSkPoint())) { *pt = (*fLine)[1]; *lineT = 1; } if (fIntersections->used() > 0 && approximately_equal((*fIntersections)[1][0], *lineT)) { return false; } if (gridPt == fConic[0].asSkPoint()) { *pt = fConic[0]; *conicT = 0; } else if (gridPt == fConic[2].asSkPoint()) { *pt = fConic[2]; *conicT = 1; } return true; } bool uniqueAnswer(double conicT, const SkDPoint& pt) { for (int inner = 0; inner < fIntersections->used(); ++inner) { if (fIntersections->pt(inner) != pt) { continue; } double existingConicT = (*fIntersections)[0][inner]; if (conicT == existingConicT) { return false; } // check if midway on conic is also same point. If so, discard this double conicMidT = (existingConicT + conicT) / 2; SkDPoint conicMidPt = fConic.ptAtT(conicMidT); if (conicMidPt.approximatelyEqual(pt)) { return false; } } #if ONE_OFF_DEBUG SkDPoint qPt = fConic.ptAtT(conicT); SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, qPt.fX, qPt.fY); #endif return true; } private: const SkDConic& fConic; const SkDLine* fLine; SkIntersections* fIntersections; bool fAllowNear; }; int SkIntersections::horizontal(const SkDConic& conic, double left, double right, double y, bool flipped) { SkDLine line = {{{ left, y }, { right, y }}}; LineConicIntersections c(conic, line, this); return c.horizontalIntersect(y, left, right, flipped); } int SkIntersections::vertical(const SkDConic& conic, double top, double bottom, double x, bool flipped) { SkDLine line = {{{ x, top }, { x, bottom }}}; LineConicIntersections c(conic, line, this); return c.verticalIntersect(x, top, bottom, flipped); } int SkIntersections::intersect(const SkDConic& conic, const SkDLine& line) { LineConicIntersections c(conic, line, this); c.allowNear(fAllowNear); return c.intersect(); } int SkIntersections::intersectRay(const SkDConic& conic, const SkDLine& line) { LineConicIntersections c(conic, line, this); fUsed = c.intersectRay(fT[0]); for (int index = 0; index < fUsed; ++index) { fPt[index] = conic.ptAtT(fT[0][index]); } return fUsed; } int SkIntersections::HorizontalIntercept(const SkDConic& conic, SkScalar y, double* roots) { LineConicIntersections c(conic); return c.horizontalIntersect(y, roots); } int SkIntersections::VerticalIntercept(const SkDConic& conic, SkScalar x, double* roots) { LineConicIntersections c(conic); return c.verticalIntersect(x, roots); } /* Find the intersection of a line and cubic by solving for valid t values. Analogous to line-quadratic intersection, solve line-cubic intersection by representing the cubic as: x = a(1-t)^3 + 2b(1-t)^2t + c(1-t)t^2 + dt^3 y = e(1-t)^3 + 2f(1-t)^2t + g(1-t)t^2 + ht^3 and the line as: y = i*x + j (if the line is more horizontal) or: x = i*y + j (if the line is more vertical) Then using Mathematica, solve for the values of t where the cubic intersects the line: (in) Resultant[ a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - x, e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - i*x - j, x] (out) -e + j + 3 e t - 3 f t - 3 e t^2 + 6 f t^2 - 3 g t^2 + e t^3 - 3 f t^3 + 3 g t^3 - h t^3 + i ( a - 3 a t + 3 b t + 3 a t^2 - 6 b t^2 + 3 c t^2 - a t^3 + 3 b t^3 - 3 c t^3 + d t^3 ) if i goes to infinity, we can rewrite the line in terms of x. Mathematica: (in) Resultant[ a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - i*y - j, e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] (out) a - j - 3 a t + 3 b t + 3 a t^2 - 6 b t^2 + 3 c t^2 - a t^3 + 3 b t^3 - 3 c t^3 + d t^3 - i ( e - 3 e t + 3 f t + 3 e t^2 - 6 f t^2 + 3 g t^2 - e t^3 + 3 f t^3 - 3 g t^3 + h t^3 ) Solving this with Mathematica produces an expression with hundreds of terms; instead, use Numeric Solutions recipe to solve the cubic. The near-horizontal case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 A = (-(-e + 3*f - 3*g + h) + i*(-a + 3*b - 3*c + d) ) B = 3*(-( e - 2*f + g ) + i*( a - 2*b + c ) ) C = 3*(-(-e + f ) + i*(-a + b ) ) D = (-( e ) + i*( a ) + j ) The near-vertical case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 A = ( (-a + 3*b - 3*c + d) - i*(-e + 3*f - 3*g + h) ) B = 3*( ( a - 2*b + c ) - i*( e - 2*f + g ) ) C = 3*( (-a + b ) - i*(-e + f ) ) D = ( ( a ) - i*( e ) - j ) For horizontal lines: (in) Resultant[ a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - j, e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] (out) e - j - 3 e t + 3 f t + 3 e t^2 - 6 f t^2 + 3 g t^2 - e t^3 + 3 f t^3 - 3 g t^3 + h t^3 */ class LineCubicIntersections { public: enum PinTPoint { kPointUninitialized, kPointInitialized }; LineCubicIntersections(const SkDCubic& c, const SkDLine& l, SkIntersections* i) : fCubic(c) , fLine(l) , fIntersections(i) , fAllowNear(true) { i->setMax(4); } void allowNear(bool allow) { fAllowNear = allow; } void checkCoincident() { int last = fIntersections->used() - 1; for (int index = 0; index < last; ) { double cubicMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); double t = fLine.nearPoint(cubicMidPt, nullptr); if (t < 0) { ++index; continue; } if (fIntersections->isCoincident(index)) { fIntersections->removeOne(index); --last; } else if (fIntersections->isCoincident(index + 1)) { fIntersections->removeOne(index + 1); --last; } else { fIntersections->setCoincident(index++); } fIntersections->setCoincident(index); } } // see parallel routine in line quadratic intersections int intersectRay(double roots[3]) { double adj = fLine[1].fX - fLine[0].fX; double opp = fLine[1].fY - fLine[0].fY; SkDCubic c; SkDEBUGCODE(c.fDebugGlobalState = fIntersections->globalState()); for (int n = 0; n < 4; ++n) { c[n].fX = (fCubic[n].fY - fLine[0].fY) * adj - (fCubic[n].fX - fLine[0].fX) * opp; } double A, B, C, D; SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D); int count = SkDCubic::RootsValidT(A, B, C, D, roots); for (int index = 0; index < count; ++index) { SkDPoint calcPt = c.ptAtT(roots[index]); if (!approximately_zero(calcPt.fX)) { for (int n = 0; n < 4; ++n) { c[n].fY = (fCubic[n].fY - fLine[0].fY) * opp + (fCubic[n].fX - fLine[0].fX) * adj; } double extremeTs[6]; int extrema = SkDCubic::FindExtrema(&c[0].fX, extremeTs); count = c.searchRoots(extremeTs, extrema, 0, SkDCubic::kXAxis, roots); break; } } return count; } int intersect() { addExactEndPoints(); if (fAllowNear) { addNearEndPoints(); } double rootVals[3]; int roots = intersectRay(rootVals); for (int index = 0; index < roots; ++index) { double cubicT = rootVals[index]; double lineT = findLineT(cubicT); SkDPoint pt; if (pinTs(&cubicT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(cubicT, pt)) { fIntersections->insert(cubicT, lineT, pt); } } checkCoincident(); return fIntersections->used(); } static int HorizontalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) { double A, B, C, D; SkDCubic::Coefficients(&c[0].fY, &A, &B, &C, &D); D -= axisIntercept; int count = SkDCubic::RootsValidT(A, B, C, D, roots); for (int index = 0; index < count; ++index) { SkDPoint calcPt = c.ptAtT(roots[index]); if (!approximately_equal(calcPt.fY, axisIntercept)) { double extremeTs[6]; int extrema = SkDCubic::FindExtrema(&c[0].fY, extremeTs); count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kYAxis, roots); break; } } return count; } int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { addExactHorizontalEndPoints(left, right, axisIntercept); if (fAllowNear) { addNearHorizontalEndPoints(left, right, axisIntercept); } double roots[3]; int count = HorizontalIntersect(fCubic, axisIntercept, roots); for (int index = 0; index < count; ++index) { double cubicT = roots[index]; SkDPoint pt = { fCubic.ptAtT(cubicT).fX, axisIntercept }; double lineT = (pt.fX - left) / (right - left); if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { fIntersections->insert(cubicT, lineT, pt); } } if (flipped) { fIntersections->flip(); } checkCoincident(); return fIntersections->used(); } bool uniqueAnswer(double cubicT, const SkDPoint& pt) { for (int inner = 0; inner < fIntersections->used(); ++inner) { if (fIntersections->pt(inner) != pt) { continue; } double existingCubicT = (*fIntersections)[0][inner]; if (cubicT == existingCubicT) { return false; } // check if midway on cubic is also same point. If so, discard this double cubicMidT = (existingCubicT + cubicT) / 2; SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); if (cubicMidPt.approximatelyEqual(pt)) { return false; } } #if ONE_OFF_DEBUG SkDPoint cPt = fCubic.ptAtT(cubicT); SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, cPt.fX, cPt.fY); #endif return true; } static int VerticalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) { double A, B, C, D; SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D); D -= axisIntercept; int count = SkDCubic::RootsValidT(A, B, C, D, roots); for (int index = 0; index < count; ++index) { SkDPoint calcPt = c.ptAtT(roots[index]); if (!approximately_equal(calcPt.fX, axisIntercept)) { double extremeTs[6]; int extrema = SkDCubic::FindExtrema(&c[0].fX, extremeTs); count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kXAxis, roots); break; } } return count; } int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { addExactVerticalEndPoints(top, bottom, axisIntercept); if (fAllowNear) { addNearVerticalEndPoints(top, bottom, axisIntercept); } double roots[3]; int count = VerticalIntersect(fCubic, axisIntercept, roots); for (int index = 0; index < count; ++index) { double cubicT = roots[index]; SkDPoint pt = { axisIntercept, fCubic.ptAtT(cubicT).fY }; double lineT = (pt.fY - top) / (bottom - top); if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { fIntersections->insert(cubicT, lineT, pt); } } if (flipped) { fIntersections->flip(); } checkCoincident(); return fIntersections->used(); } protected: void addExactEndPoints() { for (int cIndex = 0; cIndex < 4; cIndex += 3) { double lineT = fLine.exactPoint(fCubic[cIndex]); if (lineT < 0) { continue; } double cubicT = (double) (cIndex >> 1); fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } } /* Note that this does not look for endpoints of the line that are near the cubic. These points are found later when check ends looks for missing points */ void addNearEndPoints() { for (int cIndex = 0; cIndex < 4; cIndex += 3) { double cubicT = (double) (cIndex >> 1); if (fIntersections->hasT(cubicT)) { continue; } double lineT = fLine.nearPoint(fCubic[cIndex], nullptr); if (lineT < 0) { continue; } fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } this->addLineNearEndPoints(); } void addLineNearEndPoints() { for (int lIndex = 0; lIndex < 2; ++lIndex) { double lineT = (double) lIndex; if (fIntersections->hasOppT(lineT)) { continue; } double cubicT = ((SkDCurve*) &fCubic)->nearPoint(SkPath::kCubic_Verb, fLine[lIndex], fLine[!lIndex]); if (cubicT < 0) { continue; } fIntersections->insert(cubicT, lineT, fLine[lIndex]); } } void addExactHorizontalEndPoints(double left, double right, double y) { for (int cIndex = 0; cIndex < 4; cIndex += 3) { double lineT = SkDLine::ExactPointH(fCubic[cIndex], left, right, y); if (lineT < 0) { continue; } double cubicT = (double) (cIndex >> 1); fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } } void addNearHorizontalEndPoints(double left, double right, double y) { for (int cIndex = 0; cIndex < 4; cIndex += 3) { double cubicT = (double) (cIndex >> 1); if (fIntersections->hasT(cubicT)) { continue; } double lineT = SkDLine::NearPointH(fCubic[cIndex], left, right, y); if (lineT < 0) { continue; } fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } this->addLineNearEndPoints(); } void addExactVerticalEndPoints(double top, double bottom, double x) { for (int cIndex = 0; cIndex < 4; cIndex += 3) { double lineT = SkDLine::ExactPointV(fCubic[cIndex], top, bottom, x); if (lineT < 0) { continue; } double cubicT = (double) (cIndex >> 1); fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } } void addNearVerticalEndPoints(double top, double bottom, double x) { for (int cIndex = 0; cIndex < 4; cIndex += 3) { double cubicT = (double) (cIndex >> 1); if (fIntersections->hasT(cubicT)) { continue; } double lineT = SkDLine::NearPointV(fCubic[cIndex], top, bottom, x); if (lineT < 0) { continue; } fIntersections->insert(cubicT, lineT, fCubic[cIndex]); } this->addLineNearEndPoints(); } double findLineT(double t) { SkDPoint xy = fCubic.ptAtT(t); double dx = fLine[1].fX - fLine[0].fX; double dy = fLine[1].fY - fLine[0].fY; if (fabs(dx) > fabs(dy)) { return (xy.fX - fLine[0].fX) / dx; } return (xy.fY - fLine[0].fY) / dy; } bool pinTs(double* cubicT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { if (!approximately_one_or_less(*lineT)) { return false; } if (!approximately_zero_or_more(*lineT)) { return false; } double cT = *cubicT = SkPinT(*cubicT); double lT = *lineT = SkPinT(*lineT); SkDPoint lPt = fLine.ptAtT(lT); SkDPoint cPt = fCubic.ptAtT(cT); if (!lPt.roughlyEqual(cPt)) { return false; } // FIXME: if points are roughly equal but not approximately equal, need to do // a binary search like quad/quad intersection to find more precise t values if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && cT != 0 && cT != 1)) { *pt = lPt; } else if (ptSet == kPointUninitialized) { *pt = cPt; } SkPoint gridPt = pt->asSkPoint(); if (gridPt == fLine[0].asSkPoint()) { *lineT = 0; } else if (gridPt == fLine[1].asSkPoint()) { *lineT = 1; } if (gridPt == fCubic[0].asSkPoint() && approximately_equal(*cubicT, 0)) { *cubicT = 0; } else if (gridPt == fCubic[3].asSkPoint() && approximately_equal(*cubicT, 1)) { *cubicT = 1; } return true; } private: const SkDCubic& fCubic; const SkDLine& fLine; SkIntersections* fIntersections; bool fAllowNear; }; int SkIntersections::horizontal(const SkDCubic& cubic, double left, double right, double y, bool flipped) { SkDLine line = {{{ left, y }, { right, y }}}; LineCubicIntersections c(cubic, line, this); return c.horizontalIntersect(y, left, right, flipped); } int SkIntersections::vertical(const SkDCubic& cubic, double top, double bottom, double x, bool flipped) { SkDLine line = {{{ x, top }, { x, bottom }}}; LineCubicIntersections c(cubic, line, this); return c.verticalIntersect(x, top, bottom, flipped); } int SkIntersections::intersect(const SkDCubic& cubic, const SkDLine& line) { LineCubicIntersections c(cubic, line, this); c.allowNear(fAllowNear); return c.intersect(); } int SkIntersections::intersectRay(const SkDCubic& cubic, const SkDLine& line) { LineCubicIntersections c(cubic, line, this); fUsed = c.intersectRay(fT[0]); for (int index = 0; index < fUsed; ++index) { fPt[index] = cubic.ptAtT(fT[0][index]); } return fUsed; } // SkDCubic accessors to Intersection utilities int SkDCubic::horizontalIntersect(double yIntercept, double roots[3]) const { return LineCubicIntersections::HorizontalIntersect(*this, yIntercept, roots); } int SkDCubic::verticalIntersect(double xIntercept, double roots[3]) const { return LineCubicIntersections::VerticalIntersect(*this, xIntercept, roots); } void SkIntersections::cleanUpParallelLines(bool parallel) { while (fUsed > 2) { removeOne(1); } if (fUsed == 2 && !parallel) { bool startMatch = fT[0][0] == 0 || zero_or_one(fT[1][0]); bool endMatch = fT[0][1] == 1 || zero_or_one(fT[1][1]); if ((!startMatch && !endMatch) || approximately_equal(fT[0][0], fT[0][1])) { SkASSERT(startMatch || endMatch); if (startMatch && endMatch && (fT[0][0] != 0 || !zero_or_one(fT[1][0])) && fT[0][1] == 1 && zero_or_one(fT[1][1])) { removeOne(0); } else { removeOne(endMatch); } } } if (fUsed == 2) { fIsCoincident[0] = fIsCoincident[1] = 0x03; } } void SkIntersections::computePoints(const SkDLine& line, int used) { fPt[0] = line.ptAtT(fT[0][0]); if ((fUsed = used) == 2) { fPt[1] = line.ptAtT(fT[0][1]); } } int SkIntersections::intersectRay(const SkDLine& a, const SkDLine& b) { fMax = 2; SkDVector aLen = a[1] - a[0]; SkDVector bLen = b[1] - b[0]; /* Slopes match when denom goes to zero: axLen / ayLen == bxLen / byLen (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen byLen * axLen == ayLen * bxLen byLen * axLen - ayLen * bxLen == 0 ( == denom ) */ double denom = bLen.fY * aLen.fX - aLen.fY * bLen.fX; int used; if (!approximately_zero(denom)) { SkDVector ab0 = a[0] - b[0]; double numerA = ab0.fY * bLen.fX - bLen.fY * ab0.fX; double numerB = ab0.fY * aLen.fX - aLen.fY * ab0.fX; numerA /= denom; numerB /= denom; fT[0][0] = numerA; fT[1][0] = numerB; used = 1; } else { /* See if the axis intercepts match: ay - ax * ayLen / axLen == by - bx * ayLen / axLen axLen * (ay - ax * ayLen / axLen) == axLen * (by - bx * ayLen / axLen) axLen * ay - ax * ayLen == axLen * by - bx * ayLen */ if (!AlmostEqualUlps(aLen.fX * a[0].fY - aLen.fY * a[0].fX, aLen.fX * b[0].fY - aLen.fY * b[0].fX)) { return fUsed = 0; } // there's no great answer for intersection points for coincident rays, but return something fT[0][0] = fT[1][0] = 0; fT[1][0] = fT[1][1] = 1; used = 2; } computePoints(a, used); return fUsed; } // note that this only works if both lines are neither horizontal nor vertical int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) { fMax = 3; // note that we clean up so that there is no more than two in the end // see if end points intersect the opposite line double t; for (int iA = 0; iA < 2; ++iA) { if ((t = b.exactPoint(a[iA])) >= 0) { insert(iA, t, a[iA]); } } for (int iB = 0; iB < 2; ++iB) { if ((t = a.exactPoint(b[iB])) >= 0) { insert(t, iB, b[iB]); } } /* Determine the intersection point of two line segments Return FALSE if the lines don't intersect from: http://paulbourke.net/geometry/lineline2d/ */ double axLen = a[1].fX - a[0].fX; double ayLen = a[1].fY - a[0].fY; double bxLen = b[1].fX - b[0].fX; double byLen = b[1].fY - b[0].fY; /* Slopes match when denom goes to zero: axLen / ayLen == bxLen / byLen (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen byLen * axLen == ayLen * bxLen byLen * axLen - ayLen * bxLen == 0 ( == denom ) */ double axByLen = axLen * byLen; double ayBxLen = ayLen * bxLen; // detect parallel lines the same way here and in SkOpAngle operator < // so that non-parallel means they are also sortable bool unparallel = fAllowNear ? NotAlmostEqualUlps_Pin(axByLen, ayBxLen) : NotAlmostDequalUlps(axByLen, ayBxLen); if (unparallel && fUsed == 0) { double ab0y = a[0].fY - b[0].fY; double ab0x = a[0].fX - b[0].fX; double numerA = ab0y * bxLen - byLen * ab0x; double numerB = ab0y * axLen - ayLen * ab0x; double denom = axByLen - ayBxLen; if (between(0, numerA, denom) && between(0, numerB, denom)) { fT[0][0] = numerA / denom; fT[1][0] = numerB / denom; computePoints(a, 1); } } /* Allow tracking that both sets of end points are near each other -- the lines are entirely coincident -- even when the end points are not exactly the same. Mark this as a 'wild card' for the end points, so that either point is considered totally coincident. Then, avoid folding the lines over each other, but allow either end to mate to the next set of lines. */ if (fAllowNear || !unparallel) { double aNearB[2]; double bNearA[2]; bool aNotB[2] = {false, false}; bool bNotA[2] = {false, false}; int nearCount = 0; for (int index = 0; index < 2; ++index) { aNearB[index] = t = b.nearPoint(a[index], &aNotB[index]); nearCount += t >= 0; bNearA[index] = t = a.nearPoint(b[index], &bNotA[index]); nearCount += t >= 0; } if (nearCount > 0) { // Skip if each segment contributes to one end point. if (nearCount != 2 || aNotB[0] == aNotB[1]) { for (int iA = 0; iA < 2; ++iA) { if (!aNotB[iA]) { continue; } int nearer = aNearB[iA] > 0.5; if (!bNotA[nearer]) { continue; } SkASSERT(a[iA] != b[nearer]); SkOPASSERT(iA == (bNearA[nearer] > 0.5)); insertNear(iA, nearer, a[iA], b[nearer]); aNearB[iA] = -1; bNearA[nearer] = -1; nearCount -= 2; } } if (nearCount > 0) { for (int iA = 0; iA < 2; ++iA) { if (aNearB[iA] >= 0) { insert(iA, aNearB[iA], a[iA]); } } for (int iB = 0; iB < 2; ++iB) { if (bNearA[iB] >= 0) { insert(bNearA[iB], iB, b[iB]); } } } } } cleanUpParallelLines(!unparallel); SkASSERT(fUsed <= 2); return fUsed; } static int horizontal_coincident(const SkDLine& line, double y) { double min = line[0].fY; double max = line[1].fY; if (min > max) { using std::swap; swap(min, max); } if (min > y || max < y) { return 0; } if (AlmostEqualUlps(min, max) && max - min < fabs(line[0].fX - line[1].fX)) { return 2; } return 1; } double SkIntersections::HorizontalIntercept(const SkDLine& line, double y) { SkASSERT(line[1].fY != line[0].fY); return SkPinT((y - line[0].fY) / (line[1].fY - line[0].fY)); } int SkIntersections::horizontal(const SkDLine& line, double left, double right, double y, bool flipped) { fMax = 3; // clean up parallel at the end will limit the result to 2 at the most // see if end points intersect the opposite line double t; const SkDPoint leftPt = { left, y }; if ((t = line.exactPoint(leftPt)) >= 0) { insert(t, (double) flipped, leftPt); } if (left != right) { const SkDPoint rightPt = { right, y }; if ((t = line.exactPoint(rightPt)) >= 0) { insert(t, (double) !flipped, rightPt); } for (int index = 0; index < 2; ++index) { if ((t = SkDLine::ExactPointH(line[index], left, right, y)) >= 0) { insert((double) index, flipped ? 1 - t : t, line[index]); } } } int result = horizontal_coincident(line, y); if (result == 1 && fUsed == 0) { fT[0][0] = HorizontalIntercept(line, y); double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX); if (between(left, xIntercept, right)) { fT[1][0] = (xIntercept - left) / (right - left); if (flipped) { // OPTIMIZATION: ? instead of swapping, pass original line, use [1].fX - [0].fX for (int index = 0; index < result; ++index) { fT[1][index] = 1 - fT[1][index]; } } fPt[0].fX = xIntercept; fPt[0].fY = y; fUsed = 1; } } if (fAllowNear || result == 2) { if ((t = line.nearPoint(leftPt, nullptr)) >= 0) { insert(t, (double) flipped, leftPt); } if (left != right) { const SkDPoint rightPt = { right, y }; if ((t = line.nearPoint(rightPt, nullptr)) >= 0) { insert(t, (double) !flipped, rightPt); } for (int index = 0; index < 2; ++index) { if ((t = SkDLine::NearPointH(line[index], left, right, y)) >= 0) { insert((double) index, flipped ? 1 - t : t, line[index]); } } } } cleanUpParallelLines(result == 2); return fUsed; } static int vertical_coincident(const SkDLine& line, double x) { double min = line[0].fX; double max = line[1].fX; if (min > max) { using std::swap; swap(min, max); } if (!precisely_between(min, x, max)) { return 0; } if (AlmostEqualUlps(min, max)) { return 2; } return 1; } double SkIntersections::VerticalIntercept(const SkDLine& line, double x) { SkASSERT(line[1].fX != line[0].fX); return SkPinT((x - line[0].fX) / (line[1].fX - line[0].fX)); } int SkIntersections::vertical(const SkDLine& line, double top, double bottom, double x, bool flipped) { fMax = 3; // cleanup parallel lines will bring this back line // see if end points intersect the opposite line double t; SkDPoint topPt = { x, top }; if ((t = line.exactPoint(topPt)) >= 0) { insert(t, (double) flipped, topPt); } if (top != bottom) { SkDPoint bottomPt = { x, bottom }; if ((t = line.exactPoint(bottomPt)) >= 0) { insert(t, (double) !flipped, bottomPt); } for (int index = 0; index < 2; ++index) { if ((t = SkDLine::ExactPointV(line[index], top, bottom, x)) >= 0) { insert((double) index, flipped ? 1 - t : t, line[index]); } } } int result = vertical_coincident(line, x); if (result == 1 && fUsed == 0) { fT[0][0] = VerticalIntercept(line, x); double yIntercept = line[0].fY + fT[0][0] * (line[1].fY - line[0].fY); if (between(top, yIntercept, bottom)) { fT[1][0] = (yIntercept - top) / (bottom - top); if (flipped) { // OPTIMIZATION: instead of swapping, pass original line, use [1].fY - [0].fY for (int index = 0; index < result; ++index) { fT[1][index] = 1 - fT[1][index]; } } fPt[0].fX = x; fPt[0].fY = yIntercept; fUsed = 1; } } if (fAllowNear || result == 2) { if ((t = line.nearPoint(topPt, nullptr)) >= 0) { insert(t, (double) flipped, topPt); } if (top != bottom) { SkDPoint bottomPt = { x, bottom }; if ((t = line.nearPoint(bottomPt, nullptr)) >= 0) { insert(t, (double) !flipped, bottomPt); } for (int index = 0; index < 2; ++index) { if ((t = SkDLine::NearPointV(line[index], top, bottom, x)) >= 0) { insert((double) index, flipped ? 1 - t : t, line[index]); } } } } cleanUpParallelLines(result == 2); SkASSERT(fUsed <= 2); return fUsed; } /* Find the intersection of a line and quadratic by solving for valid t values. From http://stackoverflow.com/questions/1853637/how-to-find-the-mathematical-function-defining-a-bezier-curve "A Bezier curve is a parametric function. A quadratic Bezier curve (i.e. three control points) can be expressed as: F(t) = A(1 - t)^2 + B(1 - t)t + Ct^2 where A, B and C are points and t goes from zero to one. This will give you two equations: x = a(1 - t)^2 + b(1 - t)t + ct^2 y = d(1 - t)^2 + e(1 - t)t + ft^2 If you add for instance the line equation (y = kx + m) to that, you'll end up with three equations and three unknowns (x, y and t)." Similar to above, the quadratic is represented as x = a(1-t)^2 + 2b(1-t)t + ct^2 y = d(1-t)^2 + 2e(1-t)t + ft^2 and the line as y = g*x + h Using Mathematica, solve for the values of t where the quadratic intersects the line: (in) t1 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - x, d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - g*x - h, x] (out) -d + h + 2 d t - 2 e t - d t^2 + 2 e t^2 - f t^2 + g (a - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2) (in) Solve[t1 == 0, t] (out) { {t -> (-2 d + 2 e + 2 a g - 2 b g - Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / (2 (-d + 2 e - f + a g - 2 b g + c g)) }, {t -> (-2 d + 2 e + 2 a g - 2 b g + Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / (2 (-d + 2 e - f + a g - 2 b g + c g)) } } Using the results above (when the line tends towards horizontal) A = (-(d - 2*e + f) + g*(a - 2*b + c) ) B = 2*( (d - e ) - g*(a - b ) ) C = (-(d ) + g*(a ) + h ) If g goes to infinity, we can rewrite the line in terms of x. x = g'*y + h' And solve accordingly in Mathematica: (in) t2 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - g'*y - h', d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - y, y] (out) a - h' - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2 - g' (d - 2 d t + 2 e t + d t^2 - 2 e t^2 + f t^2) (in) Solve[t2 == 0, t] (out) { {t -> (2 a - 2 b - 2 d g' + 2 e g' - Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')]) / (2 (a - 2 b + c - d g' + 2 e g' - f g')) }, {t -> (2 a - 2 b - 2 d g' + 2 e g' + Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')])/ (2 (a - 2 b + c - d g' + 2 e g' - f g')) } } Thus, if the slope of the line tends towards vertical, we use: A = ( (a - 2*b + c) - g'*(d - 2*e + f) ) B = 2*(-(a - b ) + g'*(d - e ) ) C = ( (a ) - g'*(d ) - h' ) */ class LineQuadraticIntersections { public: enum PinTPoint { kPointUninitialized, kPointInitialized }; LineQuadraticIntersections(const SkDQuad& q, const SkDLine& l, SkIntersections* i) : fQuad(q) , fLine(&l) , fIntersections(i) , fAllowNear(true) { i->setMax(5); // allow short partial coincidence plus discrete intersections } LineQuadraticIntersections(const SkDQuad& q) : fQuad(q) SkDEBUGPARAMS(fLine(nullptr)) SkDEBUGPARAMS(fIntersections(nullptr)) SkDEBUGPARAMS(fAllowNear(false)) { } void allowNear(bool allow) { fAllowNear = allow; } void checkCoincident() { int last = fIntersections->used() - 1; for (int index = 0; index < last; ) { double quadMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); double t = fLine->nearPoint(quadMidPt, nullptr); if (t < 0) { ++index; continue; } if (fIntersections->isCoincident(index)) { fIntersections->removeOne(index); --last; } else if (fIntersections->isCoincident(index + 1)) { fIntersections->removeOne(index + 1); --last; } else { fIntersections->setCoincident(index++); } fIntersections->setCoincident(index); } } int intersectRay(double roots[2]) { /* solve by rotating line+quad so line is horizontal, then finding the roots set up matrix to rotate quad to x-axis |cos(a) -sin(a)| |sin(a) cos(a)| note that cos(a) = A(djacent) / Hypoteneuse sin(a) = O(pposite) / Hypoteneuse since we are computing Ts, we can ignore hypoteneuse, the scale factor: | A -O | | O A | A = line[1].fX - line[0].fX (adjacent side of the right triangle) O = line[1].fY - line[0].fY (opposite side of the right triangle) for each of the three points (e.g. n = 0 to 2) quad[n].fY' = (quad[n].fY - line[0].fY) * A - (quad[n].fX - line[0].fX) * O */ double adj = (*fLine)[1].fX - (*fLine)[0].fX; double opp = (*fLine)[1].fY - (*fLine)[0].fY; double r[3]; for (int n = 0; n < 3; ++n) { r[n] = (fQuad[n].fY - (*fLine)[0].fY) * adj - (fQuad[n].fX - (*fLine)[0].fX) * opp; } double A = r[2]; double B = r[1]; double C = r[0]; A += C - 2 * B; // A = a - 2*b + c B -= C; // B = -(b - c) return SkDQuad::RootsValidT(A, 2 * B, C, roots); } int intersect() { addExactEndPoints(); if (fAllowNear) { addNearEndPoints(); } double rootVals[2]; int roots = intersectRay(rootVals); for (int index = 0; index < roots; ++index) { double quadT = rootVals[index]; double lineT = findLineT(quadT); SkDPoint pt; if (pinTs(&quadT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(quadT, pt)) { fIntersections->insert(quadT, lineT, pt); } } checkCoincident(); return fIntersections->used(); } int horizontalIntersect(double axisIntercept, double roots[2]) { double D = fQuad[2].fY; // f double E = fQuad[1].fY; // e double F = fQuad[0].fY; // d D += F - 2 * E; // D = d - 2*e + f E -= F; // E = -(d - e) F -= axisIntercept; return SkDQuad::RootsValidT(D, 2 * E, F, roots); } int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { addExactHorizontalEndPoints(left, right, axisIntercept); if (fAllowNear) { addNearHorizontalEndPoints(left, right, axisIntercept); } double rootVals[2]; int roots = horizontalIntersect(axisIntercept, rootVals); for (int index = 0; index < roots; ++index) { double quadT = rootVals[index]; SkDPoint pt = fQuad.ptAtT(quadT); double lineT = (pt.fX - left) / (right - left); if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { fIntersections->insert(quadT, lineT, pt); } } if (flipped) { fIntersections->flip(); } checkCoincident(); return fIntersections->used(); } bool uniqueAnswer(double quadT, const SkDPoint& pt) { for (int inner = 0; inner < fIntersections->used(); ++inner) { if (fIntersections->pt(inner) != pt) { continue; } double existingQuadT = (*fIntersections)[0][inner]; if (quadT == existingQuadT) { return false; } // check if midway on quad is also same point. If so, discard this double quadMidT = (existingQuadT + quadT) / 2; SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); if (quadMidPt.approximatelyEqual(pt)) { return false; } } #if ONE_OFF_DEBUG SkDPoint qPt = fQuad.ptAtT(quadT); SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, qPt.fX, qPt.fY); #endif return true; } int verticalIntersect(double axisIntercept, double roots[2]) { double D = fQuad[2].fX; // f double E = fQuad[1].fX; // e double F = fQuad[0].fX; // d D += F - 2 * E; // D = d - 2*e + f E -= F; // E = -(d - e) F -= axisIntercept; return SkDQuad::RootsValidT(D, 2 * E, F, roots); } int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { addExactVerticalEndPoints(top, bottom, axisIntercept); if (fAllowNear) { addNearVerticalEndPoints(top, bottom, axisIntercept); } double rootVals[2]; int roots = verticalIntersect(axisIntercept, rootVals); for (int index = 0; index < roots; ++index) { double quadT = rootVals[index]; SkDPoint pt = fQuad.ptAtT(quadT); double lineT = (pt.fY - top) / (bottom - top); if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { fIntersections->insert(quadT, lineT, pt); } } if (flipped) { fIntersections->flip(); } checkCoincident(); return fIntersections->used(); } protected: // add endpoints first to get zero and one t values exactly void addExactEndPoints() { for (int qIndex = 0; qIndex < 3; qIndex += 2) { double lineT = fLine->exactPoint(fQuad[qIndex]); if (lineT < 0) { continue; } double quadT = (double) (qIndex >> 1); fIntersections->insert(quadT, lineT, fQuad[qIndex]); } } void addNearEndPoints() { for (int qIndex = 0; qIndex < 3; qIndex += 2) { double quadT = (double) (qIndex >> 1); if (fIntersections->hasT(quadT)) { continue; } double lineT = fLine->nearPoint(fQuad[qIndex], nullptr); if (lineT < 0) { continue; } fIntersections->insert(quadT, lineT, fQuad[qIndex]); } this->addLineNearEndPoints(); } void addLineNearEndPoints() { for (int lIndex = 0; lIndex < 2; ++lIndex) { double lineT = (double) lIndex; if (fIntersections->hasOppT(lineT)) { continue; } double quadT = ((SkDCurve*) &fQuad)->nearPoint(SkPath::kQuad_Verb, (*fLine)[lIndex], (*fLine)[!lIndex]); if (quadT < 0) { continue; } fIntersections->insert(quadT, lineT, (*fLine)[lIndex]); } } void addExactHorizontalEndPoints(double left, double right, double y) { for (int qIndex = 0; qIndex < 3; qIndex += 2) { double lineT = SkDLine::ExactPointH(fQuad[qIndex], left, right, y); if (lineT < 0) { continue; } double quadT = (double) (qIndex >> 1); fIntersections->insert(quadT, lineT, fQuad[qIndex]); } } void addNearHorizontalEndPoints(double left, double right, double y) { for (int qIndex = 0; qIndex < 3; qIndex += 2) { double quadT = (double) (qIndex >> 1); if (fIntersections->hasT(quadT)) { continue; } double lineT = SkDLine::NearPointH(fQuad[qIndex], left, right, y); if (lineT < 0) { continue; } fIntersections->insert(quadT, lineT, fQuad[qIndex]); } this->addLineNearEndPoints(); } void addExactVerticalEndPoints(double top, double bottom, double x) { for (int qIndex = 0; qIndex < 3; qIndex += 2) { double lineT = SkDLine::ExactPointV(fQuad[qIndex], top, bottom, x); if (lineT < 0) { continue; } double quadT = (double) (qIndex >> 1); fIntersections->insert(quadT, lineT, fQuad[qIndex]); } } void addNearVerticalEndPoints(double top, double bottom, double x) { for (int qIndex = 0; qIndex < 3; qIndex += 2) { double quadT = (double) (qIndex >> 1); if (fIntersections->hasT(quadT)) { continue; } double lineT = SkDLine::NearPointV(fQuad[qIndex], top, bottom, x); if (lineT < 0) { continue; } fIntersections->insert(quadT, lineT, fQuad[qIndex]); } this->addLineNearEndPoints(); } double findLineT(double t) { SkDPoint xy = fQuad.ptAtT(t); double dx = (*fLine)[1].fX - (*fLine)[0].fX; double dy = (*fLine)[1].fY - (*fLine)[0].fY; if (fabs(dx) > fabs(dy)) { return (xy.fX - (*fLine)[0].fX) / dx; } return (xy.fY - (*fLine)[0].fY) / dy; } bool pinTs(double* quadT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { if (!approximately_one_or_less_double(*lineT)) { return false; } if (!approximately_zero_or_more_double(*lineT)) { return false; } double qT = *quadT = SkPinT(*quadT); double lT = *lineT = SkPinT(*lineT); if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && qT != 0 && qT != 1)) { *pt = (*fLine).ptAtT(lT); } else if (ptSet == kPointUninitialized) { *pt = fQuad.ptAtT(qT); } SkPoint gridPt = pt->asSkPoint(); if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[0].asSkPoint())) { *pt = (*fLine)[0]; *lineT = 0; } else if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[1].asSkPoint())) { *pt = (*fLine)[1]; *lineT = 1; } if (fIntersections->used() > 0 && approximately_equal((*fIntersections)[1][0], *lineT)) { return false; } if (gridPt == fQuad[0].asSkPoint()) { *pt = fQuad[0]; *quadT = 0; } else if (gridPt == fQuad[2].asSkPoint()) { *pt = fQuad[2]; *quadT = 1; } return true; } private: const SkDQuad& fQuad; const SkDLine* fLine; SkIntersections* fIntersections; bool fAllowNear; }; int SkIntersections::horizontal(const SkDQuad& quad, double left, double right, double y, bool flipped) { SkDLine line = {{{ left, y }, { right, y }}}; LineQuadraticIntersections q(quad, line, this); return q.horizontalIntersect(y, left, right, flipped); } int SkIntersections::vertical(const SkDQuad& quad, double top, double bottom, double x, bool flipped) { SkDLine line = {{{ x, top }, { x, bottom }}}; LineQuadraticIntersections q(quad, line, this); return q.verticalIntersect(x, top, bottom, flipped); } int SkIntersections::intersect(const SkDQuad& quad, const SkDLine& line) { LineQuadraticIntersections q(quad, line, this); q.allowNear(fAllowNear); return q.intersect(); } int SkIntersections::intersectRay(const SkDQuad& quad, const SkDLine& line) { LineQuadraticIntersections q(quad, line, this); fUsed = q.intersectRay(fT[0]); for (int index = 0; index < fUsed; ++index) { fPt[index] = quad.ptAtT(fT[0][index]); } return fUsed; } int SkIntersections::HorizontalIntercept(const SkDQuad& quad, SkScalar y, double* roots) { LineQuadraticIntersections q(quad); return q.horizontalIntersect(y, roots); } int SkIntersections::VerticalIntercept(const SkDQuad& quad, SkScalar x, double* roots) { LineQuadraticIntersections q(quad); return q.verticalIntersect(x, roots); } // SkDQuad accessors to Intersection utilities int SkDQuad::horizontalIntersect(double yIntercept, double roots[2]) const { return SkIntersections::HorizontalIntercept(*this, yIntercept, roots); } int SkDQuad::verticalIntersect(double xIntercept, double roots[2]) const { return SkIntersections::VerticalIntercept(*this, xIntercept, roots); } int SkIntersections::closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, double* closestDist) const { int closest = -1; *closestDist = SK_ScalarMax; for (int index = 0; index < fUsed; ++index) { if (!between(rangeStart, fT[0][index], rangeEnd)) { continue; } const SkDPoint& iPt = fPt[index]; double dist = testPt.distanceSquared(iPt); if (*closestDist > dist) { *closestDist = dist; closest = index; } } return closest; } void SkIntersections::flip() { for (int index = 0; index < fUsed; ++index) { fT[1][index] = 1 - fT[1][index]; } } int SkIntersections::insert(double one, double two, const SkDPoint& pt) { if (fIsCoincident[0] == 3 && between(fT[0][0], one, fT[0][1])) { // For now, don't allow a mix of coincident and non-coincident intersections return -1; } SkASSERT(fUsed <= 1 || fT[0][0] <= fT[0][1]); int index; for (index = 0; index < fUsed; ++index) { double oldOne = fT[0][index]; double oldTwo = fT[1][index]; if (one == oldOne && two == oldTwo) { return -1; } if (more_roughly_equal(oldOne, one) && more_roughly_equal(oldTwo, two)) { if ((!precisely_zero(one) || precisely_zero(oldOne)) && (!precisely_equal(one, 1) || precisely_equal(oldOne, 1)) && (!precisely_zero(two) || precisely_zero(oldTwo)) && (!precisely_equal(two, 1) || precisely_equal(oldTwo, 1))) { return -1; } SkASSERT(one >= 0 && one <= 1); SkASSERT(two >= 0 && two <= 1); // remove this and reinsert below in case replacing would make list unsorted int remaining = fUsed - index - 1; memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); int clearMask = ~((1 << index) - 1); fIsCoincident[0] -= (fIsCoincident[0] >> 1) & clearMask; fIsCoincident[1] -= (fIsCoincident[1] >> 1) & clearMask; --fUsed; break; } #if ONE_OFF_DEBUG if (pt.roughlyEqual(fPt[index])) { SkDebugf("%s t=%1.9g pts roughly equal\n", __FUNCTION__, one); } #endif } for (index = 0; index < fUsed; ++index) { if (fT[0][index] > one) { break; } } if (fUsed >= fMax) { SkOPASSERT(0); // FIXME : this error, if it is to be handled at runtime in release, must // be propagated all the way back down to the caller, and return failure. fUsed = 0; return 0; } int remaining = fUsed - index; if (remaining > 0) { memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining); memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining); int clearMask = ~((1 << index) - 1); fIsCoincident[0] += fIsCoincident[0] & clearMask; fIsCoincident[1] += fIsCoincident[1] & clearMask; } fPt[index] = pt; if (one < 0 || one > 1) { return -1; } if (two < 0 || two > 1) { return -1; } fT[0][index] = one; fT[1][index] = two; ++fUsed; SkASSERT(fUsed <= std::size(fPt)); return index; } void SkIntersections::insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2) { SkASSERT(one == 0 || one == 1); SkASSERT(two == 0 || two == 1); SkASSERT(pt1 != pt2); fNearlySame[one ? 1 : 0] = true; (void) insert(one, two, pt1); fPt2[one ? 1 : 0] = pt2; } int SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) { int index = insertSwap(one, two, pt); if (index >= 0) { setCoincident(index); } return index; } void SkIntersections::setCoincident(int index) { SkASSERT(index >= 0); int bit = 1 << index; fIsCoincident[0] |= bit; fIsCoincident[1] |= bit; } void SkIntersections::merge(const SkIntersections& a, int aIndex, const SkIntersections& b, int bIndex) { this->reset(); fT[0][0] = a.fT[0][aIndex]; fT[1][0] = b.fT[0][bIndex]; fPt[0] = a.fPt[aIndex]; fPt2[0] = b.fPt[bIndex]; fUsed = 1; } int SkIntersections::mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const { int result = -1; for (int index = 0; index < fUsed; ++index) { if (!between(rangeStart, fT[0][index], rangeEnd)) { continue; } if (result < 0) { result = index; continue; } SkDVector best = fPt[result] - origin; SkDVector test = fPt[index] - origin; if (test.crossCheck(best) < 0) { result = index; } } return result; } void SkIntersections::removeOne(int index) { int remaining = --fUsed - index; if (remaining <= 0) { return; } memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); // SkASSERT(fIsCoincident[0] == 0); int coBit = fIsCoincident[0] & (1 << index); fIsCoincident[0] -= ((fIsCoincident[0] >> 1) & ~((1 << index) - 1)) + coBit; SkASSERT(!(coBit ^ (fIsCoincident[1] & (1 << index)))); fIsCoincident[1] -= ((fIsCoincident[1] >> 1) & ~((1 << index) - 1)) + coBit; } /* Angles are sorted counterclockwise. The smallest angle has a positive x and the smallest positive y. The largest angle has a positive x and a zero y. */ #if DEBUG_ANGLE static bool CompareResult(const char* func, SkString* bugOut, SkString* bugPart, int append, bool compare) { SkDebugf("%s %c %d\n", bugOut->c_str(), compare ? 'T' : 'F', append); SkDebugf("%sPart %s\n", func, bugPart[0].c_str()); SkDebugf("%sPart %s\n", func, bugPart[1].c_str()); SkDebugf("%sPart %s\n", func, bugPart[2].c_str()); return compare; } #define COMPARE_RESULT(append, compare) CompareResult(__FUNCTION__, &bugOut, bugPart, append, \ compare) #else #define COMPARE_RESULT(append, compare) compare #endif /* quarter angle values for sector 31 x > 0, y == 0 horizontal line (to the right) 0 x > 0, y == epsilon quad/cubic horizontal tangent eventually going +y 1 x > 0, y > 0, x > y nearer horizontal angle 2 x + e == y quad/cubic 45 going horiz 3 x > 0, y > 0, x == y 45 angle 4 x == y + e quad/cubic 45 going vert 5 x > 0, y > 0, x < y nearer vertical angle 6 x == epsilon, y > 0 quad/cubic vertical tangent eventually going +x 7 x == 0, y > 0 vertical line (to the top) 8 7 6 9 | 5 10 | 4 11 | 3 12 \ | / 2 13 | 1 14 | 0 15 --------------+------------- 31 16 | 30 17 | 29 18 / | \ 28 19 | 27 20 | 26 21 | 25 22 23 24 */ // return true if lh < this < rh bool SkOpAngle::after(SkOpAngle* test) { SkOpAngle* lh = test; SkOpAngle* rh = lh->fNext; SkASSERT(lh != rh); fPart.fCurve = fOriginalCurvePart; // Adjust lh and rh to share the same origin (floating point error in intersections can mean // they aren't exactly the same). lh->fPart.fCurve = lh->fOriginalCurvePart; lh->fPart.fCurve[0] = fPart.fCurve[0]; rh->fPart.fCurve = rh->fOriginalCurvePart; rh->fPart.fCurve[0] = fPart.fCurve[0]; #if DEBUG_ANGLE SkString bugOut; bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, lh->fStart->t(), lh->fEnd->t(), segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, rh->fStart->t(), rh->fEnd->t()); SkString bugPart[3] = { lh->debugPart(), this->debugPart(), rh->debugPart() }; #endif if (lh->fComputeSector && !lh->computeSector()) { return COMPARE_RESULT(1, true); } if (fComputeSector && !this->computeSector()) { return COMPARE_RESULT(2, true); } if (rh->fComputeSector && !rh->computeSector()) { return COMPARE_RESULT(3, true); } #if DEBUG_ANGLE // reset bugOut with computed sectors bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, lh->fStart->t(), lh->fEnd->t(), segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, rh->fStart->t(), rh->fEnd->t()); #endif bool ltrOverlap = (lh->fSectorMask | rh->fSectorMask) & fSectorMask; bool lrOverlap = lh->fSectorMask & rh->fSectorMask; int lrOrder; // set to -1 if either order works if (!lrOverlap) { // no lh/rh sector overlap if (!ltrOverlap) { // no lh/this/rh sector overlap return COMPARE_RESULT(4, (lh->fSectorEnd > rh->fSectorStart) ^ (fSectorStart > lh->fSectorEnd) ^ (fSectorStart > rh->fSectorStart)); } int lrGap = (rh->fSectorStart - lh->fSectorStart + 32) & 0x1f; /* A tiny change can move the start +/- 4. The order can only be determined if lr gap is not 12 to 20 or -12 to -20. -31 ..-21 1 -20 ..-12 -1 -11 .. -1 0 0 shouldn't get here 11 .. 1 1 12 .. 20 -1 21 .. 31 0 */ lrOrder = lrGap > 20 ? 0 : lrGap > 11 ? -1 : 1; } else { lrOrder = lh->orderable(rh); if (!ltrOverlap && lrOrder >= 0) { return COMPARE_RESULT(5, !lrOrder); } } int ltOrder; SkASSERT((lh->fSectorMask & fSectorMask) || (rh->fSectorMask & fSectorMask) || -1 == lrOrder); if (lh->fSectorMask & fSectorMask) { ltOrder = lh->orderable(this); } else { int ltGap = (fSectorStart - lh->fSectorStart + 32) & 0x1f; ltOrder = ltGap > 20 ? 0 : ltGap > 11 ? -1 : 1; } int trOrder; if (rh->fSectorMask & fSectorMask) { trOrder = this->orderable(rh); } else { int trGap = (rh->fSectorStart - fSectorStart + 32) & 0x1f; trOrder = trGap > 20 ? 0 : trGap > 11 ? -1 : 1; } this->alignmentSameSide(lh, <Order); this->alignmentSameSide(rh, &trOrder); if (lrOrder >= 0 && ltOrder >= 0 && trOrder >= 0) { return COMPARE_RESULT(7, lrOrder ? (ltOrder & trOrder) : (ltOrder | trOrder)); } // SkASSERT(lrOrder >= 0 || ltOrder >= 0 || trOrder >= 0); // There's not enough information to sort. Get the pairs of angles in opposite planes. // If an order is < 0, the pair is already in an opposite plane. Check the remaining pairs. // FIXME : once all variants are understood, rewrite this more simply if (ltOrder == 0 && lrOrder == 0) { SkASSERT(trOrder < 0); // FIXME : once this is verified to work, remove one opposite angle call SkDEBUGCODE(bool lrOpposite = lh->oppositePlanes(rh)); bool ltOpposite = lh->oppositePlanes(this); SkOPASSERT(lrOpposite != ltOpposite); return COMPARE_RESULT(8, ltOpposite); } else if (ltOrder == 1 && trOrder == 0) { SkASSERT(lrOrder < 0); bool trOpposite = oppositePlanes(rh); return COMPARE_RESULT(9, trOpposite); } else if (lrOrder == 1 && trOrder == 1) { SkASSERT(ltOrder < 0); // SkDEBUGCODE(bool trOpposite = oppositePlanes(rh)); bool lrOpposite = lh->oppositePlanes(rh); // SkASSERT(lrOpposite != trOpposite); return COMPARE_RESULT(10, lrOpposite); } // If a pair couldn't be ordered, there's not enough information to determine the sort. // Refer to: https://docs.google.com/drawings/d/1KV-8SJTedku9fj4K6fd1SB-8divuV_uivHVsSgwXICQ if (fUnorderable || lh->fUnorderable || rh->fUnorderable) { // limit to lines; should work with curves, but wait for a failing test to verify if (!fPart.isCurve() && !lh->fPart.isCurve() && !rh->fPart.isCurve()) { // see if original raw data is orderable // if two share a point, check if third has both points in same half plane int ltShare = lh->fOriginalCurvePart[0] == fOriginalCurvePart[0]; int lrShare = lh->fOriginalCurvePart[0] == rh->fOriginalCurvePart[0]; int trShare = fOriginalCurvePart[0] == rh->fOriginalCurvePart[0]; // if only one pair are the same, the third point touches neither of the pair if (ltShare + lrShare + trShare == 1) { if (lrShare) { int ltOOrder = lh->linesOnOriginalSide(this); int rtOOrder = rh->linesOnOriginalSide(this); if ((rtOOrder ^ ltOOrder) == 1) { return ltOOrder; } } else if (trShare) { int tlOOrder = this->linesOnOriginalSide(lh); int rlOOrder = rh->linesOnOriginalSide(lh); if ((tlOOrder ^ rlOOrder) == 1) { return rlOOrder; } } else { SkASSERT(ltShare); int trOOrder = rh->linesOnOriginalSide(this); int lrOOrder = lh->linesOnOriginalSide(rh); // result must be 0 and 1 or 1 and 0 to be valid if ((lrOOrder ^ trOOrder) == 1) { return trOOrder; } } } } } if (lrOrder < 0) { if (ltOrder < 0) { return COMPARE_RESULT(11, trOrder); } return COMPARE_RESULT(12, ltOrder); } return COMPARE_RESULT(13, !lrOrder); } int SkOpAngle::lineOnOneSide(const SkDPoint& origin, const SkDVector& line, const SkOpAngle* test, bool useOriginal) const { double crosses[3]; SkPath::Verb testVerb = test->segment()->verb(); int iMax = SkPathOpsVerbToPoints(testVerb); // SkASSERT(origin == test.fCurveHalf[0]); const SkDCurve& testCurve = useOriginal ? test->fOriginalCurvePart : test->fPart.fCurve; for (int index = 1; index <= iMax; ++index) { double xy1 = line.fX * (testCurve[index].fY - origin.fY); double xy2 = line.fY * (testCurve[index].fX - origin.fX); crosses[index - 1] = AlmostBequalUlps(xy1, xy2) ? 0 : xy1 - xy2; } if (crosses[0] * crosses[1] < 0) { return -1; } if (SkPath::kCubic_Verb == testVerb) { if (crosses[0] * crosses[2] < 0 || crosses[1] * crosses[2] < 0) { return -1; } } if (crosses[0]) { return crosses[0] < 0; } if (crosses[1]) { return crosses[1] < 0; } if (SkPath::kCubic_Verb == testVerb && crosses[2]) { return crosses[2] < 0; } return -2; } // given a line, see if the opposite curve's convex hull is all on one side // returns -1=not on one side 0=this CW of test 1=this CCW of test int SkOpAngle::lineOnOneSide(const SkOpAngle* test, bool useOriginal) { SkASSERT(!fPart.isCurve()); SkASSERT(test->fPart.isCurve()); SkDPoint origin = fPart.fCurve[0]; SkDVector line = fPart.fCurve[1] - origin; int result = this->lineOnOneSide(origin, line, test, useOriginal); if (-2 == result) { fUnorderable = true; result = -1; } return result; } // experiment works only with lines for now int SkOpAngle::linesOnOriginalSide(const SkOpAngle* test) { SkASSERT(!fPart.isCurve()); SkASSERT(!test->fPart.isCurve()); SkDPoint origin = fOriginalCurvePart[0]; SkDVector line = fOriginalCurvePart[1] - origin; double dots[2]; double crosses[2]; const SkDCurve& testCurve = test->fOriginalCurvePart; for (int index = 0; index < 2; ++index) { SkDVector testLine = testCurve[index] - origin; double xy1 = line.fX * testLine.fY; double xy2 = line.fY * testLine.fX; dots[index] = line.fX * testLine.fX + line.fY * testLine.fY; crosses[index] = AlmostBequalUlps(xy1, xy2) ? 0 : xy1 - xy2; } if (crosses[0] * crosses[1] < 0) { return -1; } if (crosses[0]) { return crosses[0] < 0; } if (crosses[1]) { return crosses[1] < 0; } if ((!dots[0] && dots[1] < 0) || (dots[0] < 0 && !dots[1])) { return 2; // 180 degrees apart } fUnorderable = true; return -1; } // To sort the angles, all curves are translated to have the same starting point. // If the curve's control point in its original position is on one side of a compared line, // and translated is on the opposite side, reverse the previously computed order. void SkOpAngle::alignmentSameSide(const SkOpAngle* test, int* order) const { if (*order < 0) { return; } if (fPart.isCurve()) { // This should support all curve types, but only bug that requires this has lines // Turning on for curves causes existing tests to fail return; } if (test->fPart.isCurve()) { return; } const SkDPoint& xOrigin = test->fPart.fCurve.fLine[0]; const SkDPoint& oOrigin = test->fOriginalCurvePart.fLine[0]; if (xOrigin == oOrigin) { return; } int iMax = SkPathOpsVerbToPoints(this->segment()->verb()); SkDVector xLine = test->fPart.fCurve.fLine[1] - xOrigin; SkDVector oLine = test->fOriginalCurvePart.fLine[1] - oOrigin; for (int index = 1; index <= iMax; ++index) { const SkDPoint& testPt = fPart.fCurve[index]; double xCross = oLine.crossCheck(testPt - xOrigin); double oCross = xLine.crossCheck(testPt - oOrigin); if (oCross * xCross < 0) { *order ^= 1; break; } } } bool SkOpAngle::checkCrossesZero() const { int start = std::min(fSectorStart, fSectorEnd); int end = std::max(fSectorStart, fSectorEnd); bool crossesZero = end - start > 16; return crossesZero; } bool SkOpAngle::checkParallel(SkOpAngle* rh) { SkDVector scratch[2]; const SkDVector* sweep, * tweep; if (this->fPart.isOrdered()) { sweep = this->fPart.fSweep; } else { scratch[0] = this->fPart.fCurve[1] - this->fPart.fCurve[0]; sweep = &scratch[0]; } if (rh->fPart.isOrdered()) { tweep = rh->fPart.fSweep; } else { scratch[1] = rh->fPart.fCurve[1] - rh->fPart.fCurve[0]; tweep = &scratch[1]; } double s0xt0 = sweep->crossCheck(*tweep); if (tangentsDiverge(rh, s0xt0)) { return s0xt0 < 0; } // compute the perpendicular to the endpoints and see where it intersects the opposite curve // if the intersections within the t range, do a cross check on those bool inside; if (!fEnd->contains(rh->fEnd)) { if (this->endToSide(rh, &inside)) { return inside; } if (rh->endToSide(this, &inside)) { return !inside; } } if (this->midToSide(rh, &inside)) { return inside; } if (rh->midToSide(this, &inside)) { return !inside; } // compute the cross check from the mid T values (last resort) SkDVector m0 = segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0]; SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0]; double m0xm1 = m0.crossCheck(m1); if (m0xm1 == 0) { this->fUnorderable = true; rh->fUnorderable = true; return true; } return m0xm1 < 0; } // the original angle is too short to get meaningful sector information // lengthen it until it is long enough to be meaningful or leave it unset if lengthening it // would cause it to intersect one of the adjacent angles bool SkOpAngle::computeSector() { if (fComputedSector) { return !fUnorderable; } fComputedSector = true; bool stepUp = fStart->t() < fEnd->t(); SkOpSpanBase* checkEnd = fEnd; if (checkEnd->final() && stepUp) { fUnorderable = true; return false; } do { // advance end const SkOpSegment* other = checkEnd->segment(); const SkOpSpanBase* oSpan = other->head(); do { if (oSpan->segment() != segment()) { continue; } if (oSpan == checkEnd) { continue; } if (!approximately_equal(oSpan->t(), checkEnd->t())) { continue; } goto recomputeSector; } while (!oSpan->final() && (oSpan = oSpan->upCast()->next())); checkEnd = stepUp ? !checkEnd->final() ? checkEnd->upCast()->next() : nullptr : checkEnd->prev(); } while (checkEnd); recomputeSector: SkOpSpanBase* computedEnd = stepUp ? checkEnd ? checkEnd->prev() : fEnd->segment()->head() : checkEnd ? checkEnd->upCast()->next() : fEnd->segment()->tail(); if (checkEnd == fEnd || computedEnd == fEnd || computedEnd == fStart) { fUnorderable = true; return false; } if (stepUp != (fStart->t() < computedEnd->t())) { fUnorderable = true; return false; } SkOpSpanBase* saveEnd = fEnd; fComputedEnd = fEnd = computedEnd; setSpans(); setSector(); fEnd = saveEnd; return !fUnorderable; } int SkOpAngle::convexHullOverlaps(const SkOpAngle* rh) { const SkDVector* sweep = this->fPart.fSweep; const SkDVector* tweep = rh->fPart.fSweep; double s0xs1 = sweep[0].crossCheck(sweep[1]); double s0xt0 = sweep[0].crossCheck(tweep[0]); double s1xt0 = sweep[1].crossCheck(tweep[0]); bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0; double s0xt1 = sweep[0].crossCheck(tweep[1]); double s1xt1 = sweep[1].crossCheck(tweep[1]); tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0; double t0xt1 = tweep[0].crossCheck(tweep[1]); if (tBetweenS) { return -1; } if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1 return -1; } bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0; sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0; if (sBetweenT) { return -1; } // if all of the sweeps are in the same half plane, then the order of any pair is enough if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) { return 0; } if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) { return 1; } // if the outside sweeps are greater than 180 degress: // first assume the inital tangents are the ordering // if the midpoint direction matches the inital order, that is enough SkDVector m0 = this->segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0]; SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0]; double m0xm1 = m0.crossCheck(m1); if (s0xt0 > 0 && m0xm1 > 0) { return 0; } if (s0xt0 < 0 && m0xm1 < 0) { return 1; } if (tangentsDiverge(rh, s0xt0)) { return s0xt0 < 0; } return m0xm1 < 0; } // OPTIMIZATION: longest can all be either lazily computed here or precomputed in setup double SkOpAngle::distEndRatio(double dist) const { double longest = 0; const SkOpSegment& segment = *this->segment(); int ptCount = SkPathOpsVerbToPoints(segment.verb()); const SkPoint* pts = segment.pts(); for (int idx1 = 0; idx1 <= ptCount - 1; ++idx1) { for (int idx2 = idx1 + 1; idx2 <= ptCount; ++idx2) { if (idx1 == idx2) { continue; } SkDVector v; v.set(pts[idx2] - pts[idx1]); double lenSq = v.lengthSquared(); longest = std::max(longest, lenSq); } } return sqrt(longest) / dist; } bool SkOpAngle::endsIntersect(SkOpAngle* rh) { SkPath::Verb lVerb = this->segment()->verb(); SkPath::Verb rVerb = rh->segment()->verb(); int lPts = SkPathOpsVerbToPoints(lVerb); int rPts = SkPathOpsVerbToPoints(rVerb); SkDLine rays[] = {{{this->fPart.fCurve[0], rh->fPart.fCurve[rPts]}}, {{this->fPart.fCurve[0], this->fPart.fCurve[lPts]}}}; if (this->fEnd->contains(rh->fEnd)) { return checkParallel(rh); } double smallTs[2] = {-1, -1}; bool limited[2] = {false, false}; for (int index = 0; index < 2; ++index) { SkPath::Verb cVerb = index ? rVerb : lVerb; // if the curve is a line, then the line and the ray intersect only at their crossing if (cVerb == SkPath::kLine_Verb) { continue; } const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); SkIntersections i; (*CurveIntersectRay[cVerb])(segment.pts(), segment.weight(), rays[index], &i); double tStart = index ? rh->fStart->t() : this->fStart->t(); double tEnd = index ? rh->fComputedEnd->t() : this->fComputedEnd->t(); bool testAscends = tStart < (index ? rh->fComputedEnd->t() : this->fComputedEnd->t()); double t = testAscends ? 0 : 1; for (int idx2 = 0; idx2 < i.used(); ++idx2) { double testT = i[0][idx2]; if (!approximately_between_orderable(tStart, testT, tEnd)) { continue; } if (approximately_equal_orderable(tStart, testT)) { continue; } smallTs[index] = t = testAscends ? std::max(t, testT) : std::min(t, testT); limited[index] = approximately_equal_orderable(t, tEnd); } } bool sRayLonger = false; SkDVector sCept = {0, 0}; double sCeptT = -1; int sIndex = -1; bool useIntersect = false; for (int index = 0; index < 2; ++index) { if (smallTs[index] < 0) { continue; } const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); const SkDPoint& dPt = segment.dPtAtT(smallTs[index]); SkDVector cept = dPt - rays[index][0]; // If this point is on the curve, it should have been detected earlier by ordinary // curve intersection. This may be hard to determine in general, but for lines, // the point could be close to or equal to its end, but shouldn't be near the start. if ((index ? lPts : rPts) == 1) { SkDVector total = rays[index][1] - rays[index][0]; if (cept.lengthSquared() * 2 < total.lengthSquared()) { continue; } } SkDVector end = rays[index][1] - rays[index][0]; if (cept.fX * end.fX < 0 || cept.fY * end.fY < 0) { continue; } double rayDist = cept.length(); double endDist = end.length(); bool rayLonger = rayDist > endDist; if (limited[0] && limited[1] && rayLonger) { useIntersect = true; sRayLonger = rayLonger; sCept = cept; sCeptT = smallTs[index]; sIndex = index; break; } double delta = fabs(rayDist - endDist); double minX, minY, maxX, maxY; minX = minY = SK_ScalarInfinity; maxX = maxY = -SK_ScalarInfinity; const SkDCurve& curve = index ? rh->fPart.fCurve : this->fPart.fCurve; int ptCount = index ? rPts : lPts; for (int idx2 = 0; idx2 <= ptCount; ++idx2) { minX = std::min(minX, curve[idx2].fX); minY = std::min(minY, curve[idx2].fY); maxX = std::max(maxX, curve[idx2].fX); maxY = std::max(maxY, curve[idx2].fY); } double maxWidth = std::max(maxX - minX, maxY - minY); delta = sk_ieee_double_divide(delta, maxWidth); // FIXME: move these magic numbers // This fixes skbug.com/8380 // Larger changes (like changing the constant in the next block) cause other // tests to fail as documented in the bug. // This could probably become a more general test: e.g., if translating the // curve causes the cross product of any control point or end point to change // sign with regard to the opposite curve's hull, treat the curves as parallel. // Moreso, this points to the general fragility of this approach of assigning // winding by sorting the angles of curves sharing a common point, as mentioned // in the bug. if (delta < 4e-3 && delta > 1e-3 && !useIntersect && fPart.isCurve() && rh->fPart.isCurve() && fOriginalCurvePart[0] != fPart.fCurve.fLine[0]) { // see if original curve is on one side of hull; translated is on the other const SkDPoint& origin = rh->fOriginalCurvePart[0]; int count = SkPathOpsVerbToPoints(rh->segment()->verb()); const SkDVector line = rh->fOriginalCurvePart[count] - origin; int originalSide = rh->lineOnOneSide(origin, line, this, true); if (originalSide >= 0) { int translatedSide = rh->lineOnOneSide(origin, line, this, false); if (originalSide != translatedSide) { continue; } } } if (delta > 1e-3 && (useIntersect ^= true)) { sRayLonger = rayLonger; sCept = cept; sCeptT = smallTs[index]; sIndex = index; } } if (useIntersect) { const SkDCurve& curve = sIndex ? rh->fPart.fCurve : this->fPart.fCurve; const SkOpSegment& segment = sIndex ? *rh->segment() : *this->segment(); double tStart = sIndex ? rh->fStart->t() : fStart->t(); SkDVector mid = segment.dPtAtT(tStart + (sCeptT - tStart) / 2) - curve[0]; double septDir = mid.crossCheck(sCept); if (!septDir) { return checkParallel(rh); } return sRayLonger ^ (sIndex == 0) ^ (septDir < 0); } else { return checkParallel(rh); } } bool SkOpAngle::endToSide(const SkOpAngle* rh, bool* inside) const { const SkOpSegment* segment = this->segment(); SkPath::Verb verb = segment->verb(); SkDLine rayEnd; rayEnd[0].set(this->fEnd->pt()); rayEnd[1] = rayEnd[0]; SkDVector slopeAtEnd = (*CurveDSlopeAtT[verb])(segment->pts(), segment->weight(), this->fEnd->t()); rayEnd[1].fX += slopeAtEnd.fY; rayEnd[1].fY -= slopeAtEnd.fX; SkIntersections iEnd; const SkOpSegment* oppSegment = rh->segment(); SkPath::Verb oppVerb = oppSegment->verb(); (*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayEnd, &iEnd); double endDist; int closestEnd = iEnd.closestTo(rh->fStart->t(), rh->fEnd->t(), rayEnd[0], &endDist); if (closestEnd < 0) { return false; } if (!endDist) { return false; } SkDPoint start; start.set(this->fStart->pt()); // OPTIMIZATION: multiple times in the code we find the max scalar double minX, minY, maxX, maxY; minX = minY = SK_ScalarInfinity; maxX = maxY = -SK_ScalarInfinity; const SkDCurve& curve = rh->fPart.fCurve; int oppPts = SkPathOpsVerbToPoints(oppVerb); for (int idx2 = 0; idx2 <= oppPts; ++idx2) { minX = std::min(minX, curve[idx2].fX); minY = std::min(minY, curve[idx2].fY); maxX = std::max(maxX, curve[idx2].fX); maxY = std::max(maxY, curve[idx2].fY); } double maxWidth = std::max(maxX - minX, maxY - minY); endDist = sk_ieee_double_divide(endDist, maxWidth); if (!(endDist >= 5e-12)) { // empirically found return false; // ! above catches NaN } const SkDPoint* endPt = &rayEnd[0]; SkDPoint oppPt = iEnd.pt(closestEnd); SkDVector vLeft = *endPt - start; SkDVector vRight = oppPt - start; double dir = vLeft.crossNoNormalCheck(vRight); if (!dir) { return false; } *inside = dir < 0; return true; } /* y<0 y==0 y>0 x<0 x==0 x>0 xy<0 xy==0 xy>0 0 x x x 1 x x x 2 x x x 3 x x x 4 x x x 5 x x x 6 x x x 7 x x x 8 x x x 9 x x x 10 x x x 11 x x x 12 x x x 13 x x x 14 x x x 15 x x x */ int SkOpAngle::findSector(SkPath::Verb verb, double x, double y) const { double absX = fabs(x); double absY = fabs(y); double xy = SkPath::kLine_Verb == verb || !AlmostEqualUlps(absX, absY) ? absX - absY : 0; // If there are four quadrants and eight octants, and since the Latin for sixteen is sedecim, // one could coin the term sedecimant for a space divided into 16 sections. // http://english.stackexchange.com/questions/133688/word-for-something-partitioned-into-16-parts static const int sedecimant[3][3][3] = { // y<0 y==0 y>0 // x<0 x==0 x>0 x<0 x==0 x>0 x<0 x==0 x>0 {{ 4, 3, 2}, { 7, -1, 15}, {10, 11, 12}}, // abs(x) < abs(y) {{ 5, -1, 1}, {-1, -1, -1}, { 9, -1, 13}}, // abs(x) == abs(y) {{ 6, 3, 0}, { 7, -1, 15}, { 8, 11, 14}}, // abs(x) > abs(y) }; int sector = sedecimant[(xy >= 0) + (xy > 0)][(y >= 0) + (y > 0)][(x >= 0) + (x > 0)] * 2 + 1; // SkASSERT(SkPath::kLine_Verb == verb || sector >= 0); return sector; } SkOpGlobalState* SkOpAngle::globalState() const { return this->segment()->globalState(); } // OPTIMIZE: if this loops to only one other angle, after first compare fails, insert on other side // OPTIMIZE: return where insertion succeeded. Then, start next insertion on opposite side bool SkOpAngle::insert(SkOpAngle* angle) { if (angle->fNext) { if (loopCount() >= angle->loopCount()) { if (!merge(angle)) { return true; } } else if (fNext) { if (!angle->merge(this)) { return true; } } else { angle->insert(this); } return true; } bool singleton = nullptr == fNext; if (singleton) { fNext = this; } SkOpAngle* next = fNext; if (next->fNext == this) { if (singleton || angle->after(this)) { this->fNext = angle; angle->fNext = next; } else { next->fNext = angle; angle->fNext = this; } debugValidateNext(); return true; } SkOpAngle* last = this; bool flipAmbiguity = false; do { SkASSERT(last->fNext == next); if (angle->after(last) ^ (angle->tangentsAmbiguous() & flipAmbiguity)) { last->fNext = angle; angle->fNext = next; debugValidateNext(); break; } last = next; if (last == this) { FAIL_IF(flipAmbiguity); // We're in a loop. If a sort was ambiguous, flip it to end the loop. flipAmbiguity = true; } next = next->fNext; } while (true); return true; } SkOpSpanBase* SkOpAngle::lastMarked() const { if (fLastMarked) { if (fLastMarked->chased()) { return nullptr; } fLastMarked->setChased(true); } return fLastMarked; } bool SkOpAngle::loopContains(const SkOpAngle* angle) const { if (!fNext) { return false; } const SkOpAngle* first = this; const SkOpAngle* loop = this; const SkOpSegment* tSegment = angle->fStart->segment(); double tStart = angle->fStart->t(); double tEnd = angle->fEnd->t(); do { const SkOpSegment* lSegment = loop->fStart->segment(); if (lSegment != tSegment) { continue; } double lStart = loop->fStart->t(); if (lStart != tEnd) { continue; } double lEnd = loop->fEnd->t(); if (lEnd == tStart) { return true; } } while ((loop = loop->fNext) != first); return false; } int SkOpAngle::loopCount() const { int count = 0; const SkOpAngle* first = this; const SkOpAngle* next = this; do { next = next->fNext; ++count; } while (next && next != first); return count; } bool SkOpAngle::merge(SkOpAngle* angle) { SkASSERT(fNext); SkASSERT(angle->fNext); SkOpAngle* working = angle; do { if (this == working) { return false; } working = working->fNext; } while (working != angle); do { SkOpAngle* next = working->fNext; working->fNext = nullptr; insert(working); working = next; } while (working != angle); // it's likely that a pair of the angles are unorderable debugValidateNext(); return true; } double SkOpAngle::midT() const { return (fStart->t() + fEnd->t()) / 2; } bool SkOpAngle::midToSide(const SkOpAngle* rh, bool* inside) const { const SkOpSegment* segment = this->segment(); SkPath::Verb verb = segment->verb(); const SkPoint& startPt = this->fStart->pt(); const SkPoint& endPt = this->fEnd->pt(); SkDPoint dStartPt; dStartPt.set(startPt); SkDLine rayMid; rayMid[0].fX = (startPt.fX + endPt.fX) / 2; rayMid[0].fY = (startPt.fY + endPt.fY) / 2; rayMid[1].fX = rayMid[0].fX + (endPt.fY - startPt.fY); rayMid[1].fY = rayMid[0].fY - (endPt.fX - startPt.fX); SkIntersections iMid; (*CurveIntersectRay[verb])(segment->pts(), segment->weight(), rayMid, &iMid); int iOutside = iMid.mostOutside(this->fStart->t(), this->fEnd->t(), dStartPt); if (iOutside < 0) { return false; } const SkOpSegment* oppSegment = rh->segment(); SkPath::Verb oppVerb = oppSegment->verb(); SkIntersections oppMid; (*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayMid, &oppMid); int oppOutside = oppMid.mostOutside(rh->fStart->t(), rh->fEnd->t(), dStartPt); if (oppOutside < 0) { return false; } SkDVector iSide = iMid.pt(iOutside) - dStartPt; SkDVector oppSide = oppMid.pt(oppOutside) - dStartPt; double dir = iSide.crossCheck(oppSide); if (!dir) { return false; } *inside = dir < 0; return true; } bool SkOpAngle::oppositePlanes(const SkOpAngle* rh) const { int startSpan = SkTAbs(rh->fSectorStart - fSectorStart); return startSpan >= 8; } int SkOpAngle::orderable(SkOpAngle* rh) { int result; if (!fPart.isCurve()) { if (!rh->fPart.isCurve()) { double leftX = fTangentHalf.dx(); double leftY = fTangentHalf.dy(); double rightX = rh->fTangentHalf.dx(); double rightY = rh->fTangentHalf.dy(); double x_ry = leftX * rightY; double rx_y = rightX * leftY; if (x_ry == rx_y) { if (leftX * rightX < 0 || leftY * rightY < 0) { return 1; // exactly 180 degrees apart } goto unorderable; } SkASSERT(x_ry != rx_y); // indicates an undetected coincidence -- worth finding earlier return x_ry < rx_y ? 1 : 0; } if ((result = this->lineOnOneSide(rh, false)) >= 0) { return result; } if (fUnorderable || approximately_zero(rh->fSide)) { goto unorderable; } } else if (!rh->fPart.isCurve()) { if ((result = rh->lineOnOneSide(this, false)) >= 0) { return result ? 0 : 1; } if (rh->fUnorderable || approximately_zero(fSide)) { goto unorderable; } } else if ((result = this->convexHullOverlaps(rh)) >= 0) { return result; } return this->endsIntersect(rh) ? 1 : 0; unorderable: fUnorderable = true; rh->fUnorderable = true; return -1; } // OPTIMIZE: if this shows up in a profile, add a previous pointer // as is, this should be rarely called SkOpAngle* SkOpAngle::previous() const { SkOpAngle* last = fNext; do { SkOpAngle* next = last->fNext; if (next == this) { return last; } last = next; } while (true); } SkOpSegment* SkOpAngle::segment() const { return fStart->segment(); } void SkOpAngle::set(SkOpSpanBase* start, SkOpSpanBase* end) { fStart = start; fComputedEnd = fEnd = end; SkASSERT(start != end); fNext = nullptr; fComputeSector = fComputedSector = fCheckCoincidence = fTangentsAmbiguous = false; setSpans(); setSector(); SkDEBUGCODE(fID = start ? start->globalState()->nextAngleID() : -1); } void SkOpAngle::setSpans() { fUnorderable = false; fLastMarked = nullptr; if (!fStart) { fUnorderable = true; return; } const SkOpSegment* segment = fStart->segment(); const SkPoint* pts = segment->pts(); SkDEBUGCODE(fPart.fCurve.fVerb = SkPath::kCubic_Verb); // required for SkDCurve debug check SkDEBUGCODE(fPart.fCurve[2].fX = fPart.fCurve[2].fY = fPart.fCurve[3].fX = fPart.fCurve[3].fY = SK_ScalarNaN); // make the non-line part uninitialized SkDEBUGCODE(fPart.fCurve.fVerb = segment->verb()); // set the curve type for real segment->subDivide(fStart, fEnd, &fPart.fCurve); // set at least the line part if not more fOriginalCurvePart = fPart.fCurve; const SkPath::Verb verb = segment->verb(); fPart.setCurveHullSweep(verb); if (SkPath::kLine_Verb != verb && !fPart.isCurve()) { SkDLine lineHalf; fPart.fCurve[1] = fPart.fCurve[SkPathOpsVerbToPoints(verb)]; fOriginalCurvePart[1] = fPart.fCurve[1]; lineHalf[0].set(fPart.fCurve[0].asSkPoint()); lineHalf[1].set(fPart.fCurve[1].asSkPoint()); fTangentHalf.lineEndPoints(lineHalf); fSide = 0; } switch (verb) { case SkPath::kLine_Verb: { SkASSERT(fStart != fEnd); const SkPoint& cP1 = pts[fStart->t() < fEnd->t()]; SkDLine lineHalf; lineHalf[0].set(fStart->pt()); lineHalf[1].set(cP1); fTangentHalf.lineEndPoints(lineHalf); fSide = 0; } return; case SkPath::kQuad_Verb: case SkPath::kConic_Verb: { SkLineParameters tangentPart; (void) tangentPart.quadEndPoints(fPart.fCurve.fQuad); fSide = -tangentPart.pointDistance(fPart.fCurve[2]); // not normalized -- compare sign only } break; case SkPath::kCubic_Verb: { SkLineParameters tangentPart; (void) tangentPart.cubicPart(fPart.fCurve.fCubic); fSide = -tangentPart.pointDistance(fPart.fCurve[3]); double testTs[4]; // OPTIMIZATION: keep inflections precomputed with cubic segment? int testCount = SkDCubic::FindInflections(pts, testTs); double startT = fStart->t(); double endT = fEnd->t(); double limitT = endT; int index; for (index = 0; index < testCount; ++index) { if (!::between(startT, testTs[index], limitT)) { testTs[index] = -1; } } testTs[testCount++] = startT; testTs[testCount++] = endT; SkTQSort(testTs, testTs + testCount); double bestSide = 0; int testCases = (testCount << 1) - 1; index = 0; while (testTs[index] < 0) { ++index; } index <<= 1; for (; index < testCases; ++index) { int testIndex = index >> 1; double testT = testTs[testIndex]; if (index & 1) { testT = (testT + testTs[testIndex + 1]) / 2; } // OPTIMIZE: could avoid call for t == startT, endT SkDPoint pt = dcubic_xy_at_t(pts, segment->weight(), testT); SkLineParameters testPart; testPart.cubicEndPoints(fPart.fCurve.fCubic); double testSide = testPart.pointDistance(pt); if (fabs(bestSide) < fabs(testSide)) { bestSide = testSide; } } fSide = -bestSide; // compare sign only } break; default: SkASSERT(0); } } void SkOpAngle::setSector() { if (!fStart) { fUnorderable = true; return; } const SkOpSegment* segment = fStart->segment(); SkPath::Verb verb = segment->verb(); fSectorStart = this->findSector(verb, fPart.fSweep[0].fX, fPart.fSweep[0].fY); if (fSectorStart < 0) { goto deferTilLater; } if (!fPart.isCurve()) { // if it's a line or line-like, note that both sectors are the same SkASSERT(fSectorStart >= 0); fSectorEnd = fSectorStart; fSectorMask = 1 << fSectorStart; return; } SkASSERT(SkPath::kLine_Verb != verb); fSectorEnd = this->findSector(verb, fPart.fSweep[1].fX, fPart.fSweep[1].fY); if (fSectorEnd < 0) { deferTilLater: fSectorStart = fSectorEnd = -1; fSectorMask = 0; fComputeSector = true; // can't determine sector until segment length can be found return; } if (fSectorEnd == fSectorStart && (fSectorStart & 3) != 3) { // if the sector has no span, it can't be an exact angle fSectorMask = 1 << fSectorStart; return; } bool crossesZero = this->checkCrossesZero(); int start = std::min(fSectorStart, fSectorEnd); bool curveBendsCCW = (fSectorStart == start) ^ crossesZero; // bump the start and end of the sector span if they are on exact compass points if ((fSectorStart & 3) == 3) { fSectorStart = (fSectorStart + (curveBendsCCW ? 1 : 31)) & 0x1f; } if ((fSectorEnd & 3) == 3) { fSectorEnd = (fSectorEnd + (curveBendsCCW ? 31 : 1)) & 0x1f; } crossesZero = this->checkCrossesZero(); start = std::min(fSectorStart, fSectorEnd); int end = std::max(fSectorStart, fSectorEnd); if (!crossesZero) { fSectorMask = (unsigned) -1 >> (31 - end + start) << start; } else { fSectorMask = (unsigned) -1 >> (31 - start) | ((unsigned) -1 << end); } } SkOpSpan* SkOpAngle::starter() { return fStart->starter(fEnd); } bool SkOpAngle::tangentsDiverge(const SkOpAngle* rh, double s0xt0) { if (s0xt0 == 0) { return false; } // if the ctrl tangents are not nearly parallel, use them // solve for opposite direction displacement scale factor == m // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1] // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x) // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x) // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y) // m = v1.cross(v2) / v1.dot(v2) const SkDVector* sweep = fPart.fSweep; const SkDVector* tweep = rh->fPart.fSweep; double s0dt0 = sweep[0].dot(tweep[0]); if (!s0dt0) { return true; } SkASSERT(s0dt0 != 0); double m = s0xt0 / s0dt0; double sDist = sweep[0].length() * m; double tDist = tweep[0].length() * m; bool useS = fabs(sDist) < fabs(tDist); double mFactor = fabs(useS ? this->distEndRatio(sDist) : rh->distEndRatio(tDist)); fTangentsAmbiguous = mFactor >= 50 && mFactor < 200; return mFactor < 50; // empirically found limit } // returns true if coincident span's start and end are the same bool SkCoincidentSpans::collapsed(const SkOpPtT* test) const { return (fCoinPtTStart == test && fCoinPtTEnd->contains(test)) || (fCoinPtTEnd == test && fCoinPtTStart->contains(test)) || (fOppPtTStart == test && fOppPtTEnd->contains(test)) || (fOppPtTEnd == test && fOppPtTStart->contains(test)); } // out of line since this function is referenced by address const SkOpPtT* SkCoincidentSpans::coinPtTEnd() const { return fCoinPtTEnd; } // out of line since this function is referenced by address const SkOpPtT* SkCoincidentSpans::coinPtTStart() const { return fCoinPtTStart; } // sets the span's end to the ptT referenced by the previous-next void SkCoincidentSpans::correctOneEnd( const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) ) { const SkOpPtT* origPtT = (this->*getEnd)(); const SkOpSpanBase* origSpan = origPtT->span(); const SkOpSpan* prev = origSpan->prev(); const SkOpPtT* testPtT = prev ? prev->next()->ptT() : origSpan->upCast()->next()->prev()->ptT(); if (origPtT != testPtT) { (this->*setEnd)(testPtT); } } /* Please keep this in sync with debugCorrectEnds */ // FIXME: member pointers have fallen out of favor and can be replaced with // an alternative approach. // makes all span ends agree with the segment's spans that define them void SkCoincidentSpans::correctEnds() { this->correctOneEnd(&SkCoincidentSpans::coinPtTStart, &SkCoincidentSpans::setCoinPtTStart); this->correctOneEnd(&SkCoincidentSpans::coinPtTEnd, &SkCoincidentSpans::setCoinPtTEnd); this->correctOneEnd(&SkCoincidentSpans::oppPtTStart, &SkCoincidentSpans::setOppPtTStart); this->correctOneEnd(&SkCoincidentSpans::oppPtTEnd, &SkCoincidentSpans::setOppPtTEnd); } /* Please keep this in sync with debugExpand */ // expand the range by checking adjacent spans for coincidence bool SkCoincidentSpans::expand() { bool expanded = false; const SkOpSegment* segment = coinPtTStart()->segment(); const SkOpSegment* oppSegment = oppPtTStart()->segment(); do { const SkOpSpan* start = coinPtTStart()->span()->upCast(); const SkOpSpan* prev = start->prev(); const SkOpPtT* oppPtT; if (!prev || !(oppPtT = prev->contains(oppSegment))) { break; } double midT = (prev->t() + start->t()) / 2; if (!segment->isClose(midT, oppSegment)) { break; } setStarts(prev->ptT(), oppPtT); expanded = true; } while (true); do { const SkOpSpanBase* end = coinPtTEnd()->span(); SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); if (next && next->deleted()) { break; } const SkOpPtT* oppPtT; if (!next || !(oppPtT = next->contains(oppSegment))) { break; } double midT = (end->t() + next->t()) / 2; if (!segment->isClose(midT, oppSegment)) { break; } setEnds(next->ptT(), oppPtT); expanded = true; } while (true); return expanded; } // increase the range of this span bool SkCoincidentSpans::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { bool result = false; if (fCoinPtTStart->fT > coinPtTStart->fT || (this->flipped() ? fOppPtTStart->fT < oppPtTStart->fT : fOppPtTStart->fT > oppPtTStart->fT)) { this->setStarts(coinPtTStart, oppPtTStart); result = true; } if (fCoinPtTEnd->fT < coinPtTEnd->fT || (this->flipped() ? fOppPtTEnd->fT > oppPtTEnd->fT : fOppPtTEnd->fT < oppPtTEnd->fT)) { this->setEnds(coinPtTEnd, oppPtTEnd); result = true; } return result; } // set the range of this span void SkCoincidentSpans::set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { SkASSERT(SkOpCoincidence::Ordered(coinPtTStart, oppPtTStart)); fNext = next; this->setStarts(coinPtTStart, oppPtTStart); this->setEnds(coinPtTEnd, oppPtTEnd); } // returns true if both points are inside this bool SkCoincidentSpans::contains(const SkOpPtT* s, const SkOpPtT* e) const { if (s->fT > e->fT) { using std::swap; swap(s, e); } if (s->segment() == fCoinPtTStart->segment()) { return fCoinPtTStart->fT <= s->fT && e->fT <= fCoinPtTEnd->fT; } else { SkASSERT(s->segment() == fOppPtTStart->segment()); double oppTs = fOppPtTStart->fT; double oppTe = fOppPtTEnd->fT; if (oppTs > oppTe) { using std::swap; swap(oppTs, oppTe); } return oppTs <= s->fT && e->fT <= oppTe; } } // out of line since this function is referenced by address const SkOpPtT* SkCoincidentSpans::oppPtTStart() const { return fOppPtTStart; } // out of line since this function is referenced by address const SkOpPtT* SkCoincidentSpans::oppPtTEnd() const { return fOppPtTEnd; } // A coincident span is unordered if the pairs of points in the main and opposite curves' // t values do not ascend or descend. For instance, if a tightly arced quadratic is // coincident with another curve, it may intersect it out of order. bool SkCoincidentSpans::ordered(bool* result) const { const SkOpSpanBase* start = this->coinPtTStart()->span(); const SkOpSpanBase* end = this->coinPtTEnd()->span(); const SkOpSpanBase* next = start->upCast()->next(); if (next == end) { *result = true; return true; } bool flipped = this->flipped(); const SkOpSegment* oppSeg = this->oppPtTStart()->segment(); double oppLastT = fOppPtTStart->fT; do { const SkOpPtT* opp = next->contains(oppSeg); if (!opp) { // SkOPOBJASSERT(start, 0); // may assert if coincident span isn't fully processed return false; } if ((oppLastT > opp->fT) != flipped) { *result = false; return true; } oppLastT = opp->fT; if (next == end) { break; } if (!next->upCastable()) { *result = false; return true; } next = next->upCast()->next(); } while (true); *result = true; return true; } // if there is an existing pair that overlaps the addition, extend it bool SkOpCoincidence::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { SkCoincidentSpans* test = fHead; if (!test) { return false; } const SkOpSegment* coinSeg = coinPtTStart->segment(); const SkOpSegment* oppSeg = oppPtTStart->segment(); if (!Ordered(coinPtTStart, oppPtTStart)) { using std::swap; swap(coinSeg, oppSeg); swap(coinPtTStart, oppPtTStart); swap(coinPtTEnd, oppPtTEnd); if (coinPtTStart->fT > coinPtTEnd->fT) { swap(coinPtTStart, coinPtTEnd); swap(oppPtTStart, oppPtTEnd); } } double oppMinT = std::min(oppPtTStart->fT, oppPtTEnd->fT); SkDEBUGCODE(double oppMaxT = std::max(oppPtTStart->fT, oppPtTEnd->fT)); do { if (coinSeg != test->coinPtTStart()->segment()) { continue; } if (oppSeg != test->oppPtTStart()->segment()) { continue; } double oTestMinT = std::min(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); double oTestMaxT = std::max(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); // if debug check triggers, caller failed to check if extended already exists SkASSERT(test->coinPtTStart()->fT > coinPtTStart->fT || coinPtTEnd->fT > test->coinPtTEnd()->fT || oTestMinT > oppMinT || oppMaxT > oTestMaxT); if ((test->coinPtTStart()->fT <= coinPtTEnd->fT && coinPtTStart->fT <= test->coinPtTEnd()->fT) || (oTestMinT <= oTestMaxT && oppMinT <= oTestMaxT)) { test->extend(coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); return true; } } while ((test = test->next())); return false; } // verifies that the coincidence hasn't already been added static void DebugCheckAdd(const SkCoincidentSpans* check, const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { #if DEBUG_COINCIDENCE while (check) { SkASSERT(check->coinPtTStart() != coinPtTStart || check->coinPtTEnd() != coinPtTEnd || check->oppPtTStart() != oppPtTStart || check->oppPtTEnd() != oppPtTEnd); SkASSERT(check->coinPtTStart() != oppPtTStart || check->coinPtTEnd() != oppPtTEnd || check->oppPtTStart() != coinPtTStart || check->oppPtTEnd() != coinPtTEnd); check = check->next(); } #endif } // adds a new coincident pair void SkOpCoincidence::add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, SkOpPtT* oppPtTEnd) { // OPTIMIZE: caller should have already sorted if (!Ordered(coinPtTStart, oppPtTStart)) { if (oppPtTStart->fT < oppPtTEnd->fT) { this->add(oppPtTStart, oppPtTEnd, coinPtTStart, coinPtTEnd); } else { this->add(oppPtTEnd, oppPtTStart, coinPtTEnd, coinPtTStart); } return; } SkASSERT(Ordered(coinPtTStart, oppPtTStart)); // choose the ptT at the front of the list to track coinPtTStart = coinPtTStart->span()->ptT(); coinPtTEnd = coinPtTEnd->span()->ptT(); oppPtTStart = oppPtTStart->span()->ptT(); oppPtTEnd = oppPtTEnd->span()->ptT(); SkOPASSERT(coinPtTStart->fT < coinPtTEnd->fT); SkOPASSERT(oppPtTStart->fT != oppPtTEnd->fT); SkOPASSERT(!coinPtTStart->deleted()); SkOPASSERT(!coinPtTEnd->deleted()); SkOPASSERT(!oppPtTStart->deleted()); SkOPASSERT(!oppPtTEnd->deleted()); DebugCheckAdd(fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); DebugCheckAdd(fTop, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); SkCoincidentSpans* coinRec = this->globalState()->allocator()->make(); coinRec->init(SkDEBUGCODE(fGlobalState)); coinRec->set(this->fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); fHead = coinRec; } // description below bool SkOpCoincidence::addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan) { const SkOpPtT* testPtT = testSpan->ptT(); const SkOpPtT* stopPtT = testPtT; const SkOpSegment* baseSeg = base->segment(); int escapeHatch = 100000; // this is 100 times larger than the debugLoopLimit test while ((testPtT = testPtT->next()) != stopPtT) { if (--escapeHatch <= 0) { return false; // if triggered (likely by a fuzz-generated test) too complex to succeed } const SkOpSegment* testSeg = testPtT->segment(); if (testPtT->deleted()) { continue; } if (testSeg == baseSeg) { continue; } if (testPtT->span()->ptT() != testPtT) { continue; } if (this->contains(baseSeg, testSeg, testPtT->fT)) { continue; } // intersect perp with base->ptT() with testPtT->segment() SkDVector dxdy = baseSeg->dSlopeAtT(base->t()); const SkPoint& pt = base->pt(); SkDLine ray = {{{pt.fX, pt.fY}, {pt.fX + dxdy.fY, pt.fY - dxdy.fX}}}; SkIntersections i SkDEBUGCODE((this->globalState())); (*CurveIntersectRay[testSeg->verb()])(testSeg->pts(), testSeg->weight(), ray, &i); for (int index = 0; index < i.used(); ++index) { double t = i[0][index]; if (!between(0, t, 1)) { continue; } SkDPoint oppPt = i.pt(index); if (!oppPt.approximatelyEqual(pt)) { continue; } SkOpSegment* writableSeg = const_cast(testSeg); SkOpPtT* oppStart = writableSeg->addT(t); if (oppStart == testPtT) { continue; } SkOpSpan* writableBase = const_cast(base); oppStart->span()->addOpp(writableBase); if (oppStart->deleted()) { continue; } SkOpSegment* coinSeg = base->segment(); SkOpSegment* oppSeg = oppStart->segment(); double coinTs, coinTe, oppTs, oppTe; if (Ordered(coinSeg, oppSeg)) { coinTs = base->t(); coinTe = testSpan->t(); oppTs = oppStart->fT; oppTe = testPtT->fT; } else { using std::swap; swap(coinSeg, oppSeg); coinTs = oppStart->fT; coinTe = testPtT->fT; oppTs = base->t(); oppTe = testSpan->t(); } if (coinTs > coinTe) { using std::swap; swap(coinTs, coinTe); swap(oppTs, oppTe); } bool added; FAIL_IF(!this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &added)); } } return true; } // description below bool SkOpCoincidence::addEndMovedSpans(const SkOpPtT* ptT) { FAIL_IF(!ptT->span()->upCastable()); const SkOpSpan* base = ptT->span()->upCast(); const SkOpSpan* prev = base->prev(); FAIL_IF(!prev); if (!prev->isCanceled()) { if (!this->addEndMovedSpans(base, base->prev())) { return false; } } if (!base->isCanceled()) { if (!this->addEndMovedSpans(base, base->next())) { return false; } } return true; } /* If A is coincident with B and B includes an endpoint, and A's matching point is not the endpoint (i.e., there's an implied line connecting B-end and A) then assume that the same implied line may intersect another curve close to B. Since we only care about coincidence that was undetected, look at the ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but next door) and see if the A matching point is close enough to form another coincident pair. If so, check for a new coincident span between B-end/A ptT loop and the adjacent ptT loop. */ bool SkOpCoincidence::addEndMovedSpans(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { DEBUG_SET_PHASE(); SkCoincidentSpans* span = fHead; if (!span) { return true; } fTop = span; fHead = nullptr; do { if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) { FAIL_IF(1 == span->coinPtTStart()->fT); bool onEnd = span->coinPtTStart()->fT == 0; bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); if (onEnd) { if (!oOnEnd) { // if both are on end, any nearby intersect was already found if (!this->addEndMovedSpans(span->oppPtTStart())) { return false; } } } else if (oOnEnd) { if (!this->addEndMovedSpans(span->coinPtTStart())) { return false; } } } if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { bool onEnd = span->coinPtTEnd()->fT == 1; bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); if (onEnd) { if (!oOnEnd) { if (!this->addEndMovedSpans(span->oppPtTEnd())) { return false; } } } else if (oOnEnd) { if (!this->addEndMovedSpans(span->coinPtTEnd())) { return false; } } } } while ((span = span->next())); this->restoreHead(); return true; } /* Please keep this in sync with debugAddExpanded */ // for each coincident pair, match the spans // if the spans don't match, add the missing pt to the segment and loop it in the opposite span bool SkOpCoincidence::addExpanded(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { DEBUG_SET_PHASE(); SkCoincidentSpans* coin = this->fHead; if (!coin) { return true; } do { const SkOpPtT* startPtT = coin->coinPtTStart(); const SkOpPtT* oStartPtT = coin->oppPtTStart(); double priorT = startPtT->fT; double oPriorT = oStartPtT->fT; FAIL_IF(!startPtT->contains(oStartPtT)); SkOPASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); const SkOpSpanBase* start = startPtT->span(); const SkOpSpanBase* oStart = oStartPtT->span(); const SkOpSpanBase* end = coin->coinPtTEnd()->span(); const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); FAIL_IF(oEnd->deleted()); FAIL_IF(!start->upCastable()); const SkOpSpanBase* test = start->upCast()->next(); FAIL_IF(!coin->flipped() && !oStart->upCastable()); const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); FAIL_IF(!oTest); SkOpSegment* seg = start->segment(); SkOpSegment* oSeg = oStart->segment(); while (test != end || oTest != oEnd) { const SkOpPtT* containedOpp = test->ptT()->contains(oSeg); const SkOpPtT* containedThis = oTest->ptT()->contains(seg); if (!containedOpp || !containedThis) { // choose the ends, or the first common pt-t list shared by both double nextT, oNextT; if (containedOpp) { nextT = test->t(); oNextT = containedOpp->fT; } else if (containedThis) { nextT = containedThis->fT; oNextT = oTest->t(); } else { // iterate through until a pt-t list found that contains the other const SkOpSpanBase* walk = test; const SkOpPtT* walkOpp; do { FAIL_IF(!walk->upCastable()); walk = walk->upCast()->next(); } while (!(walkOpp = walk->ptT()->contains(oSeg)) && walk != coin->coinPtTEnd()->span()); FAIL_IF(!walkOpp); nextT = walk->t(); oNextT = walkOpp->fT; } // use t ranges to guess which one is missing double startRange = nextT - priorT; FAIL_IF(!startRange); double startPart = (test->t() - priorT) / startRange; double oStartRange = oNextT - oPriorT; FAIL_IF(!oStartRange); double oStartPart = (oTest->t() - oPriorT) / oStartRange; FAIL_IF(startPart == oStartPart); bool addToOpp = !containedOpp && !containedThis ? startPart < oStartPart : !!containedThis; bool startOver = false; bool success = addToOpp ? oSeg->addExpanded( oPriorT + oStartRange * startPart, test, &startOver) : seg->addExpanded( priorT + startRange * oStartPart, oTest, &startOver); FAIL_IF(!success); if (startOver) { test = start; oTest = oStart; } end = coin->coinPtTEnd()->span(); oEnd = coin->oppPtTEnd()->span(); } if (test != end) { FAIL_IF(!test->upCastable()); priorT = test->t(); test = test->upCast()->next(); } if (oTest != oEnd) { oPriorT = oTest->t(); if (coin->flipped()) { oTest = oTest->prev(); } else { FAIL_IF(!oTest->upCastable()); oTest = oTest->upCast()->next(); } FAIL_IF(!oTest); } } } while ((coin = coin->next())); return true; } // given a t span, map the same range on the coincident span /* the curves may not scale linearly, so interpolation may only happen within known points remap over1s, over1e, cointPtTStart, coinPtTEnd to smallest range that captures over1s then repeat to capture over1e */ double SkOpCoincidence::TRange(const SkOpPtT* overS, double t, const SkOpSegment* coinSeg SkDEBUGPARAMS(const SkOpPtT* overE)) { const SkOpSpanBase* work = overS->span(); const SkOpPtT* foundStart = nullptr; const SkOpPtT* foundEnd = nullptr; const SkOpPtT* coinStart = nullptr; const SkOpPtT* coinEnd = nullptr; do { const SkOpPtT* contained = work->contains(coinSeg); if (!contained) { if (work->final()) { break; } continue; } if (work->t() <= t) { coinStart = contained; foundStart = work->ptT(); } if (work->t() >= t) { coinEnd = contained; foundEnd = work->ptT(); break; } SkASSERT(work->ptT() != overE); } while ((work = work->upCast()->next())); if (!coinStart || !coinEnd) { return 1; } // while overS->fT <=t and overS contains coinSeg double denom = foundEnd->fT - foundStart->fT; double sRatio = denom ? (t - foundStart->fT) / denom : 1; return coinStart->fT + (coinEnd->fT - coinStart->fT) * sRatio; } // return true if span overlaps existing and needs to adjust the coincident list bool SkOpCoincidence::checkOverlap(SkCoincidentSpans* check, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, double coinTs, double coinTe, double oppTs, double oppTe, SkTDArray* overlaps) const { if (!Ordered(coinSeg, oppSeg)) { if (oppTs < oppTe) { return this->checkOverlap(check, oppSeg, coinSeg, oppTs, oppTe, coinTs, coinTe, overlaps); } return this->checkOverlap(check, oppSeg, coinSeg, oppTe, oppTs, coinTe, coinTs, overlaps); } bool swapOpp = oppTs > oppTe; if (swapOpp) { using std::swap; swap(oppTs, oppTe); } do { if (check->coinPtTStart()->segment() != coinSeg) { continue; } if (check->oppPtTStart()->segment() != oppSeg) { continue; } double checkTs = check->coinPtTStart()->fT; double checkTe = check->coinPtTEnd()->fT; bool coinOutside = coinTe < checkTs || coinTs > checkTe; double oCheckTs = check->oppPtTStart()->fT; double oCheckTe = check->oppPtTEnd()->fT; if (swapOpp) { if (oCheckTs <= oCheckTe) { return false; } using std::swap; swap(oCheckTs, oCheckTe); } bool oppOutside = oppTe < oCheckTs || oppTs > oCheckTe; if (coinOutside && oppOutside) { continue; } bool coinInside = coinTe <= checkTe && coinTs >= checkTs; bool oppInside = oppTe <= oCheckTe && oppTs >= oCheckTs; if (coinInside && oppInside) { // already included, do nothing return false; } *overlaps->append() = check; // partial overlap, extend existing entry } while ((check = check->next())); return true; } /* Please keep this in sync with debugAddIfMissing() */ // note that over1s, over1e, over2s, over2e are ordered bool SkOpCoincidence::addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over2s, double tStart, double tEnd, SkOpSegment* coinSeg, SkOpSegment* oppSeg, bool* added SkDEBUGPARAMS(const SkOpPtT* over1e) SkDEBUGPARAMS(const SkOpPtT* over2e)) { SkASSERT(tStart < tEnd); SkASSERT(over1s->fT < over1e->fT); SkASSERT(between(over1s->fT, tStart, over1e->fT)); SkASSERT(between(over1s->fT, tEnd, over1e->fT)); SkASSERT(over2s->fT < over2e->fT); SkASSERT(between(over2s->fT, tStart, over2e->fT)); SkASSERT(between(over2s->fT, tEnd, over2e->fT)); SkASSERT(over1s->segment() == over1e->segment()); SkASSERT(over2s->segment() == over2e->segment()); SkASSERT(over1s->segment() == over2s->segment()); SkASSERT(over1s->segment() != coinSeg); SkASSERT(over1s->segment() != oppSeg); SkASSERT(coinSeg != oppSeg); double coinTs, coinTe, oppTs, oppTe; coinTs = TRange(over1s, tStart, coinSeg SkDEBUGPARAMS(over1e)); coinTe = TRange(over1s, tEnd, coinSeg SkDEBUGPARAMS(over1e)); SkOpSpanBase::Collapsed result = coinSeg->collapsed(coinTs, coinTe); if (SkOpSpanBase::Collapsed::kNo != result) { return SkOpSpanBase::Collapsed::kYes == result; } oppTs = TRange(over2s, tStart, oppSeg SkDEBUGPARAMS(over2e)); oppTe = TRange(over2s, tEnd, oppSeg SkDEBUGPARAMS(over2e)); result = oppSeg->collapsed(oppTs, oppTe); if (SkOpSpanBase::Collapsed::kNo != result) { return SkOpSpanBase::Collapsed::kYes == result; } if (coinTs > coinTe) { using std::swap; swap(coinTs, coinTe); swap(oppTs, oppTe); } (void) this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, added); return true; } /* Please keep this in sync with debugAddOrOverlap() */ // If this is called by addEndMovedSpans(), a returned false propogates out to an abort. // If this is called by AddIfMissing(), a returned false indicates there was nothing to add bool SkOpCoincidence::addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, double coinTs, double coinTe, double oppTs, double oppTe, bool* added) { SkTDArray overlaps; FAIL_IF(!fTop); if (!this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { return true; } if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { return true; } SkCoincidentSpans* overlap = !overlaps.empty() ? overlaps[0] : nullptr; for (int index = 1; index < overlaps.size(); ++index) { // combine overlaps before continuing SkCoincidentSpans* test = overlaps[index]; if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { overlap->setCoinPtTStart(test->coinPtTStart()); } if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { overlap->setCoinPtTEnd(test->coinPtTEnd()); } if (overlap->flipped() ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { overlap->setOppPtTStart(test->oppPtTStart()); } if (overlap->flipped() ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { overlap->setOppPtTEnd(test->oppPtTEnd()); } if (!fHead || !this->release(fHead, test)) { SkAssertResult(this->release(fTop, test)); } } const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); if (overlap && cs && ce && overlap->contains(cs, ce)) { return true; } FAIL_IF(cs == ce && cs); const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); if (overlap && os && oe && overlap->contains(os, oe)) { return true; } FAIL_IF(cs && cs->deleted()); FAIL_IF(os && os->deleted()); FAIL_IF(ce && ce->deleted()); FAIL_IF(oe && oe->deleted()); const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; FAIL_IF(csExisting && csExisting == ceExisting); // FAIL_IF(csExisting && (csExisting == ce || // csExisting->contains(ceExisting ? ceExisting : ce))); FAIL_IF(ceExisting && (ceExisting == cs || ceExisting->contains(csExisting ? csExisting : cs))); const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; FAIL_IF(osExisting && osExisting == oeExisting); FAIL_IF(osExisting && (osExisting == oe || osExisting->contains(oeExisting ? oeExisting : oe))); FAIL_IF(oeExisting && (oeExisting == os || oeExisting->contains(osExisting ? osExisting : os))); // extra line in debug code this->debugValidate(); if (!cs || !os) { SkOpPtT* csWritable = cs ? const_cast(cs) : coinSeg->addT(coinTs); if (csWritable == ce) { return true; } SkOpPtT* osWritable = os ? const_cast(os) : oppSeg->addT(oppTs); FAIL_IF(!csWritable || !osWritable); csWritable->span()->addOpp(osWritable->span()); cs = csWritable; os = osWritable->active(); FAIL_IF(!os); FAIL_IF((ce && ce->deleted()) || (oe && oe->deleted())); } if (!ce || !oe) { SkOpPtT* ceWritable = ce ? const_cast(ce) : coinSeg->addT(coinTe); SkOpPtT* oeWritable = oe ? const_cast(oe) : oppSeg->addT(oppTe); FAIL_IF(!ceWritable->span()->addOpp(oeWritable->span())); ce = ceWritable; oe = oeWritable; } this->debugValidate(); FAIL_IF(cs->deleted()); FAIL_IF(os->deleted()); FAIL_IF(ce->deleted()); FAIL_IF(oe->deleted()); FAIL_IF(cs->contains(ce) || os->contains(oe)); bool result = true; if (overlap) { if (overlap->coinPtTStart()->segment() == coinSeg) { result = overlap->extend(cs, ce, os, oe); } else { if (os->fT > oe->fT) { using std::swap; swap(cs, ce); swap(os, oe); } result = overlap->extend(os, oe, cs, ce); } #if DEBUG_COINCIDENCE_VERBOSE if (result) { overlaps[0]->debugShow(); } #endif } else { this->add(cs, ce, os, oe); #if DEBUG_COINCIDENCE_VERBOSE fHead->debugShow(); #endif } this->debugValidate(); if (result) { *added = true; } return true; } // Please keep this in sync with debugAddMissing() /* detects overlaps of different coincident runs on same segment */ /* does not detect overlaps for pairs without any segments in common */ // returns true if caller should loop again bool SkOpCoincidence::addMissing(bool* added DEBUG_COIN_DECLARE_PARAMS()) { SkCoincidentSpans* outer = fHead; *added = false; if (!outer) { return true; } fTop = outer; fHead = nullptr; do { // addifmissing can modify the list that this is walking // save head so that walker can iterate over old data unperturbed // addifmissing adds to head freely then add saved head in the end const SkOpPtT* ocs = outer->coinPtTStart(); FAIL_IF(ocs->deleted()); const SkOpSegment* outerCoin = ocs->segment(); FAIL_IF(outerCoin->done()); const SkOpPtT* oos = outer->oppPtTStart(); if (oos->deleted()) { return true; } const SkOpSegment* outerOpp = oos->segment(); SkOPASSERT(!outerOpp->done()); SkOpSegment* outerCoinWritable = const_cast(outerCoin); SkOpSegment* outerOppWritable = const_cast(outerOpp); SkCoincidentSpans* inner = outer; #ifdef SK_BUILD_FOR_FUZZER int safetyNet = 1000; #endif while ((inner = inner->next())) { #ifdef SK_BUILD_FOR_FUZZER if (!--safetyNet) { return false; } #endif this->debugValidate(); double overS, overE; const SkOpPtT* ics = inner->coinPtTStart(); FAIL_IF(ics->deleted()); const SkOpSegment* innerCoin = ics->segment(); FAIL_IF(innerCoin->done()); const SkOpPtT* ios = inner->oppPtTStart(); FAIL_IF(ios->deleted()); const SkOpSegment* innerOpp = ios->segment(); SkOPASSERT(!innerOpp->done()); SkOpSegment* innerCoinWritable = const_cast(innerCoin); SkOpSegment* innerOppWritable = const_cast(innerOpp); if (outerCoin == innerCoin) { const SkOpPtT* oce = outer->coinPtTEnd(); if (oce->deleted()) { return true; } const SkOpPtT* ice = inner->coinPtTEnd(); FAIL_IF(ice->deleted()); if (outerOpp != innerOpp && this->overlap(ocs, oce, ics, ice, &overS, &overE)) { FAIL_IF(!this->addIfMissing(ocs->starter(oce), ics->starter(ice), overS, overE, outerOppWritable, innerOppWritable, added SkDEBUGPARAMS(ocs->debugEnder(oce)) SkDEBUGPARAMS(ics->debugEnder(ice)))); } } else if (outerCoin == innerOpp) { const SkOpPtT* oce = outer->coinPtTEnd(); FAIL_IF(oce->deleted()); const SkOpPtT* ioe = inner->oppPtTEnd(); FAIL_IF(ioe->deleted()); if (outerOpp != innerCoin && this->overlap(ocs, oce, ios, ioe, &overS, &overE)) { FAIL_IF(!this->addIfMissing(ocs->starter(oce), ios->starter(ioe), overS, overE, outerOppWritable, innerCoinWritable, added SkDEBUGPARAMS(ocs->debugEnder(oce)) SkDEBUGPARAMS(ios->debugEnder(ioe)))); } } else if (outerOpp == innerCoin) { const SkOpPtT* ooe = outer->oppPtTEnd(); FAIL_IF(ooe->deleted()); const SkOpPtT* ice = inner->coinPtTEnd(); FAIL_IF(ice->deleted()); SkASSERT(outerCoin != innerOpp); if (this->overlap(oos, ooe, ics, ice, &overS, &overE)) { FAIL_IF(!this->addIfMissing(oos->starter(ooe), ics->starter(ice), overS, overE, outerCoinWritable, innerOppWritable, added SkDEBUGPARAMS(oos->debugEnder(ooe)) SkDEBUGPARAMS(ics->debugEnder(ice)))); } } else if (outerOpp == innerOpp) { const SkOpPtT* ooe = outer->oppPtTEnd(); FAIL_IF(ooe->deleted()); const SkOpPtT* ioe = inner->oppPtTEnd(); if (ioe->deleted()) { return true; } SkASSERT(outerCoin != innerCoin); if (this->overlap(oos, ooe, ios, ioe, &overS, &overE)) { FAIL_IF(!this->addIfMissing(oos->starter(ooe), ios->starter(ioe), overS, overE, outerCoinWritable, innerCoinWritable, added SkDEBUGPARAMS(oos->debugEnder(ooe)) SkDEBUGPARAMS(ios->debugEnder(ioe)))); } } this->debugValidate(); } } while ((outer = outer->next())); this->restoreHead(); return true; } bool SkOpCoincidence::addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, const SkOpSegment* seg2, const SkOpSegment* seg2o, const SkOpPtT* overS, const SkOpPtT* overE) { const SkOpPtT* s1 = overS->find(seg1); const SkOpPtT* e1 = overE->find(seg1); FAIL_IF(!s1); FAIL_IF(!e1); if (!s1->starter(e1)->span()->upCast()->windValue()) { s1 = overS->find(seg1o); e1 = overE->find(seg1o); FAIL_IF(!s1); FAIL_IF(!e1); if (!s1->starter(e1)->span()->upCast()->windValue()) { return true; } } const SkOpPtT* s2 = overS->find(seg2); const SkOpPtT* e2 = overE->find(seg2); FAIL_IF(!s2); FAIL_IF(!e2); if (!s2->starter(e2)->span()->upCast()->windValue()) { s2 = overS->find(seg2o); e2 = overE->find(seg2o); FAIL_IF(!s2); FAIL_IF(!e2); if (!s2->starter(e2)->span()->upCast()->windValue()) { return true; } } if (s1->segment() == s2->segment()) { return true; } if (s1->fT > e1->fT) { using std::swap; swap(s1, e1); swap(s2, e2); } this->add(s1, e1, s2, e2); return true; } bool SkOpCoincidence::contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const { if (this->contains(fHead, seg, opp, oppT)) { return true; } if (this->contains(fTop, seg, opp, oppT)) { return true; } return false; } bool SkOpCoincidence::contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const { if (!coin) { return false; } do { if (coin->coinPtTStart()->segment() == seg && coin->oppPtTStart()->segment() == opp && between(coin->oppPtTStart()->fT, oppT, coin->oppPtTEnd()->fT)) { return true; } if (coin->oppPtTStart()->segment() == seg && coin->coinPtTStart()->segment() == opp && between(coin->coinPtTStart()->fT, oppT, coin->coinPtTEnd()->fT)) { return true; } } while ((coin = coin->next())); return false; } bool SkOpCoincidence::contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const { const SkCoincidentSpans* test = fHead; if (!test) { return false; } const SkOpSegment* coinSeg = coinPtTStart->segment(); const SkOpSegment* oppSeg = oppPtTStart->segment(); if (!Ordered(coinPtTStart, oppPtTStart)) { using std::swap; swap(coinSeg, oppSeg); swap(coinPtTStart, oppPtTStart); swap(coinPtTEnd, oppPtTEnd); if (coinPtTStart->fT > coinPtTEnd->fT) { swap(coinPtTStart, coinPtTEnd); swap(oppPtTStart, oppPtTEnd); } } double oppMinT = std::min(oppPtTStart->fT, oppPtTEnd->fT); double oppMaxT = std::max(oppPtTStart->fT, oppPtTEnd->fT); do { if (coinSeg != test->coinPtTStart()->segment()) { continue; } if (coinPtTStart->fT < test->coinPtTStart()->fT) { continue; } if (coinPtTEnd->fT > test->coinPtTEnd()->fT) { continue; } if (oppSeg != test->oppPtTStart()->segment()) { continue; } if (oppMinT < std::min(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { continue; } if (oppMaxT > std::max(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { continue; } return true; } while ((test = test->next())); return false; } void SkOpCoincidence::correctEnds(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { DEBUG_SET_PHASE(); SkCoincidentSpans* coin = fHead; if (!coin) { return; } do { coin->correctEnds(); } while ((coin = coin->next())); } // walk span sets in parallel, moving winding from one to the other bool SkOpCoincidence::apply(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { DEBUG_SET_PHASE(); SkCoincidentSpans* coin = fHead; if (!coin) { return true; } do { SkOpSpanBase* startSpan = coin->coinPtTStartWritable()->span(); FAIL_IF(!startSpan->upCastable()); SkOpSpan* start = startSpan->upCast(); if (start->deleted()) { continue; } const SkOpSpanBase* end = coin->coinPtTEnd()->span(); FAIL_IF(start != start->starter(end)); bool flipped = coin->flipped(); SkOpSpanBase* oStartBase = (flipped ? coin->oppPtTEndWritable() : coin->oppPtTStartWritable())->span(); FAIL_IF(!oStartBase->upCastable()); SkOpSpan* oStart = oStartBase->upCast(); if (oStart->deleted()) { continue; } const SkOpSpanBase* oEnd = (flipped ? coin->oppPtTStart() : coin->oppPtTEnd())->span(); SkASSERT(oStart == oStart->starter(oEnd)); SkOpSegment* segment = start->segment(); SkOpSegment* oSegment = oStart->segment(); bool operandSwap = segment->operand() != oSegment->operand(); if (flipped) { if (oEnd->deleted()) { continue; } do { SkOpSpanBase* oNext = oStart->next(); if (oNext == oEnd) { break; } FAIL_IF(!oNext->upCastable()); oStart = oNext->upCast(); } while (true); } do { int windValue = start->windValue(); int oppValue = start->oppValue(); int oWindValue = oStart->windValue(); int oOppValue = oStart->oppValue(); // winding values are added or subtracted depending on direction and wind type // same or opposite values are summed depending on the operand value int windDiff = operandSwap ? oOppValue : oWindValue; int oWindDiff = operandSwap ? oppValue : windValue; if (!flipped) { windDiff = -windDiff; oWindDiff = -oWindDiff; } bool addToStart = windValue && (windValue > windDiff || (windValue == windDiff && oWindValue <= oWindDiff)); if (addToStart ? start->done() : oStart->done()) { addToStart ^= true; } if (addToStart) { if (operandSwap) { using std::swap; swap(oWindValue, oOppValue); } if (flipped) { windValue -= oWindValue; oppValue -= oOppValue; } else { windValue += oWindValue; oppValue += oOppValue; } if (segment->isXor()) { windValue &= 1; } if (segment->oppXor()) { oppValue &= 1; } oWindValue = oOppValue = 0; } else { if (operandSwap) { using std::swap; swap(windValue, oppValue); } if (flipped) { oWindValue -= windValue; oOppValue -= oppValue; } else { oWindValue += windValue; oOppValue += oppValue; } if (oSegment->isXor()) { oWindValue &= 1; } if (oSegment->oppXor()) { oOppValue &= 1; } windValue = oppValue = 0; } #if 0 && DEBUG_COINCIDENCE SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", segment->debugID(), start->debugID(), windValue, oppValue); SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", oSegment->debugID(), oStart->debugID(), oWindValue, oOppValue); #endif FAIL_IF(windValue <= -1); start->setWindValue(windValue); start->setOppValue(oppValue); FAIL_IF(oWindValue <= -1); oStart->setWindValue(oWindValue); oStart->setOppValue(oOppValue); if (!windValue && !oppValue) { segment->markDone(start); } if (!oWindValue && !oOppValue) { oSegment->markDone(oStart); } SkOpSpanBase* next = start->next(); SkOpSpanBase* oNext = flipped ? oStart->prev() : oStart->next(); if (next == end) { break; } FAIL_IF(!next->upCastable()); start = next->upCast(); // if the opposite ran out too soon, just reuse the last span if (!oNext || !oNext->upCastable()) { oNext = oStart; } oStart = oNext->upCast(); } while (true); } while ((coin = coin->next())); return true; } // Please keep this in sync with debugRelease() bool SkOpCoincidence::release(SkCoincidentSpans* coin, SkCoincidentSpans* remove) { SkCoincidentSpans* head = coin; SkCoincidentSpans* prev = nullptr; SkCoincidentSpans* next; do { next = coin->next(); if (coin == remove) { if (prev) { prev->setNext(next); } else if (head == fHead) { fHead = next; } else { fTop = next; } break; } prev = coin; } while ((coin = next)); return coin != nullptr; } void SkOpCoincidence::releaseDeleted(SkCoincidentSpans* coin) { if (!coin) { return; } SkCoincidentSpans* head = coin; SkCoincidentSpans* prev = nullptr; SkCoincidentSpans* next; do { next = coin->next(); if (coin->coinPtTStart()->deleted()) { SkOPASSERT(coin->flipped() ? coin->oppPtTEnd()->deleted() : coin->oppPtTStart()->deleted()); if (prev) { prev->setNext(next); } else if (head == fHead) { fHead = next; } else { fTop = next; } } else { SkOPASSERT(coin->flipped() ? !coin->oppPtTEnd()->deleted() : !coin->oppPtTStart()->deleted()); prev = coin; } } while ((coin = next)); } void SkOpCoincidence::releaseDeleted() { this->releaseDeleted(fHead); this->releaseDeleted(fTop); } void SkOpCoincidence::restoreHead() { SkCoincidentSpans** headPtr = &fHead; while (*headPtr) { headPtr = (*headPtr)->nextPtr(); } *headPtr = fTop; fTop = nullptr; // segments may have collapsed in the meantime; remove empty referenced segments headPtr = &fHead; while (*headPtr) { SkCoincidentSpans* test = *headPtr; if (test->coinPtTStart()->segment()->done() || test->oppPtTStart()->segment()->done()) { *headPtr = test->next(); continue; } headPtr = (*headPtr)->nextPtr(); } } // Please keep this in sync with debugExpand() // expand the range by checking adjacent spans for coincidence bool SkOpCoincidence::expand(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { DEBUG_SET_PHASE(); SkCoincidentSpans* coin = fHead; if (!coin) { return false; } bool expanded = false; do { if (coin->expand()) { // check to see if multiple spans expanded so they are now identical SkCoincidentSpans* test = fHead; do { if (coin == test) { continue; } if (coin->coinPtTStart() == test->coinPtTStart() && coin->oppPtTStart() == test->oppPtTStart()) { this->release(fHead, test); break; } } while ((test = test->next())); expanded = true; } } while ((coin = coin->next())); return expanded; } bool SkOpCoincidence::findOverlaps(SkOpCoincidence* overlaps DEBUG_COIN_DECLARE_PARAMS()) const { DEBUG_SET_PHASE(); overlaps->fHead = overlaps->fTop = nullptr; SkCoincidentSpans* outer = fHead; while (outer) { const SkOpSegment* outerCoin = outer->coinPtTStart()->segment(); const SkOpSegment* outerOpp = outer->oppPtTStart()->segment(); SkCoincidentSpans* inner = outer; while ((inner = inner->next())) { const SkOpSegment* innerCoin = inner->coinPtTStart()->segment(); if (outerCoin == innerCoin) { continue; // both winners are the same segment, so there's no additional overlap } const SkOpSegment* innerOpp = inner->oppPtTStart()->segment(); const SkOpPtT* overlapS; const SkOpPtT* overlapE; if ((outerOpp == innerCoin && SkOpPtT::Overlaps(outer->oppPtTStart(), outer->oppPtTEnd(),inner->coinPtTStart(), inner->coinPtTEnd(), &overlapS, &overlapE)) || (outerCoin == innerOpp && SkOpPtT::Overlaps(outer->coinPtTStart(), outer->coinPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), &overlapS, &overlapE)) || (outerOpp == innerOpp && SkOpPtT::Overlaps(outer->oppPtTStart(), outer->oppPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), &overlapS, &overlapE))) { if (!overlaps->addOverlap(outerCoin, outerOpp, innerCoin, innerOpp, overlapS, overlapE)) { return false; } } } outer = outer->next(); } return true; } void SkOpCoincidence::fixUp(SkOpPtT* deleted, const SkOpPtT* kept) { SkOPASSERT(deleted != kept); if (fHead) { this->fixUp(fHead, deleted, kept); } if (fTop) { this->fixUp(fTop, deleted, kept); } } void SkOpCoincidence::fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept) { SkCoincidentSpans* head = coin; do { if (coin->coinPtTStart() == deleted) { if (coin->coinPtTEnd()->span() == kept->span()) { this->release(head, coin); continue; } coin->setCoinPtTStart(kept); } if (coin->coinPtTEnd() == deleted) { if (coin->coinPtTStart()->span() == kept->span()) { this->release(head, coin); continue; } coin->setCoinPtTEnd(kept); } if (coin->oppPtTStart() == deleted) { if (coin->oppPtTEnd()->span() == kept->span()) { this->release(head, coin); continue; } coin->setOppPtTStart(kept); } if (coin->oppPtTEnd() == deleted) { if (coin->oppPtTStart()->span() == kept->span()) { this->release(head, coin); continue; } coin->setOppPtTEnd(kept); } } while ((coin = coin->next())); } // Please keep this in sync with debugMark() /* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ bool SkOpCoincidence::mark(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { DEBUG_SET_PHASE(); SkCoincidentSpans* coin = fHead; if (!coin) { return true; } do { SkOpSpanBase* startBase = coin->coinPtTStartWritable()->span(); FAIL_IF(!startBase->upCastable()); SkOpSpan* start = startBase->upCast(); FAIL_IF(start->deleted()); SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); SkOPASSERT(!end->deleted()); SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); SkOPASSERT(!oStart->deleted()); SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); FAIL_IF(oEnd->deleted()); bool flipped = coin->flipped(); if (flipped) { using std::swap; swap(oStart, oEnd); } /* coin and opp spans may not match up. Mark the ends, and then let the interior get marked as many times as the spans allow */ FAIL_IF(!oStart->upCastable()); start->insertCoincidence(oStart->upCast()); end->insertCoinEnd(oEnd); const SkOpSegment* segment = start->segment(); const SkOpSegment* oSegment = oStart->segment(); SkOpSpanBase* next = start; SkOpSpanBase* oNext = oStart; bool ordered; FAIL_IF(!coin->ordered(&ordered)); while ((next = next->upCast()->next()) != end) { FAIL_IF(!next->upCastable()); FAIL_IF(!next->upCast()->insertCoincidence(oSegment, flipped, ordered)); } while ((oNext = oNext->upCast()->next()) != oEnd) { FAIL_IF(!oNext->upCastable()); FAIL_IF(!oNext->upCast()->insertCoincidence(segment, flipped, ordered)); } } while ((coin = coin->next())); return true; } // Please keep in sync with debugMarkCollapsed() void SkOpCoincidence::markCollapsed(SkCoincidentSpans* coin, SkOpPtT* test) { SkCoincidentSpans* head = coin; while (coin) { if (coin->collapsed(test)) { if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { coin->coinPtTStartWritable()->segment()->markAllDone(); } if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { coin->oppPtTStartWritable()->segment()->markAllDone(); } this->release(head, coin); } coin = coin->next(); } } // Please keep in sync with debugMarkCollapsed() void SkOpCoincidence::markCollapsed(SkOpPtT* test) { markCollapsed(fHead, test); markCollapsed(fTop, test); } bool SkOpCoincidence::Ordered(const SkOpSegment* coinSeg, const SkOpSegment* oppSeg) { if (coinSeg->verb() < oppSeg->verb()) { return true; } if (coinSeg->verb() > oppSeg->verb()) { return false; } int count = (SkPathOpsVerbToPoints(coinSeg->verb()) + 1) * 2; const SkScalar* cPt = &coinSeg->pts()[0].fX; const SkScalar* oPt = &oppSeg->pts()[0].fX; for (int index = 0; index < count; ++index) { if (*cPt < *oPt) { return true; } if (*cPt > *oPt) { return false; } ++cPt; ++oPt; } return true; } bool SkOpCoincidence::overlap(const SkOpPtT* coin1s, const SkOpPtT* coin1e, const SkOpPtT* coin2s, const SkOpPtT* coin2e, double* overS, double* overE) const { SkASSERT(coin1s->segment() == coin2s->segment()); *overS = std::max(std::min(coin1s->fT, coin1e->fT), std::min(coin2s->fT, coin2e->fT)); *overE = std::min(std::max(coin1s->fT, coin1e->fT), std::max(coin2s->fT, coin2e->fT)); return *overS < *overE; } // Commented-out lines keep this in sync with debugRelease() void SkOpCoincidence::release(const SkOpSegment* deleted) { SkCoincidentSpans* coin = fHead; if (!coin) { return; } do { if (coin->coinPtTStart()->segment() == deleted || coin->coinPtTEnd()->segment() == deleted || coin->oppPtTStart()->segment() == deleted || coin->oppPtTEnd()->segment() == deleted) { this->release(fHead, coin); } } while ((coin = coin->next())); } void SkOpContour::toPath(SkPathWriter* path) const { if (!this->count()) { return; } const SkOpSegment* segment = &fHead; do { SkAssertResult(segment->addCurveTo(segment->head(), segment->tail(), path)); } while ((segment = segment->next())); path->finishContour(); path->assemble(); } void SkOpContour::toReversePath(SkPathWriter* path) const { const SkOpSegment* segment = fTail; do { SkAssertResult(segment->addCurveTo(segment->tail(), segment->head(), path)); } while ((segment = segment->prev())); path->finishContour(); path->assemble(); } SkOpSpan* SkOpContour::undoneSpan() { SkOpSegment* testSegment = &fHead; do { if (testSegment->done()) { continue; } return testSegment->undoneSpan(); } while ((testSegment = testSegment->next())); fDone = true; return nullptr; } void SkOpContourBuilder::addConic(SkPoint pts[3], SkScalar weight) { this->flush(); fContour->addConic(pts, weight); } void SkOpContourBuilder::addCubic(SkPoint pts[4]) { this->flush(); fContour->addCubic(pts); } void SkOpContourBuilder::addCurve(SkPath::Verb verb, const SkPoint pts[4], SkScalar weight) { if (SkPath::kLine_Verb == verb) { this->addLine(pts); return; } SkArenaAlloc* allocator = fContour->globalState()->allocator(); switch (verb) { case SkPath::kQuad_Verb: { SkPoint* ptStorage = allocator->makeArrayDefault(3); memcpy(ptStorage, pts, sizeof(SkPoint) * 3); this->addQuad(ptStorage); } break; case SkPath::kConic_Verb: { SkPoint* ptStorage = allocator->makeArrayDefault(3); memcpy(ptStorage, pts, sizeof(SkPoint) * 3); this->addConic(ptStorage, weight); } break; case SkPath::kCubic_Verb: { SkPoint* ptStorage = allocator->makeArrayDefault(4); memcpy(ptStorage, pts, sizeof(SkPoint) * 4); this->addCubic(ptStorage); } break; default: SkASSERT(0); } } void SkOpContourBuilder::addLine(const SkPoint pts[2]) { // if the previous line added is the exact opposite, eliminate both if (fLastIsLine) { if (fLastLine[0] == pts[1] && fLastLine[1] == pts[0]) { fLastIsLine = false; return; } else { flush(); } } memcpy(fLastLine, pts, sizeof(fLastLine)); fLastIsLine = true; } void SkOpContourBuilder::addQuad(SkPoint pts[3]) { this->flush(); fContour->addQuad(pts); } void SkOpContourBuilder::flush() { if (!fLastIsLine) return; SkArenaAlloc* allocator = fContour->globalState()->allocator(); SkPoint* ptStorage = allocator->makeArrayDefault(2); memcpy(ptStorage, fLastLine, sizeof(fLastLine)); (void) fContour->addLine(ptStorage); fLastIsLine = false; } static bool rotate(const SkDCubic& cubic, int zero, int index, SkDCubic& rotPath) { double dy = cubic[index].fY - cubic[zero].fY; double dx = cubic[index].fX - cubic[zero].fX; if (approximately_zero(dy)) { if (approximately_zero(dx)) { return false; } rotPath = cubic; if (dy) { rotPath[index].fY = cubic[zero].fY; int mask = other_two(index, zero); int side1 = index ^ mask; int side2 = zero ^ mask; if (approximately_equal(cubic[side1].fY, cubic[zero].fY)) { rotPath[side1].fY = cubic[zero].fY; } if (approximately_equal(cubic[side2].fY, cubic[zero].fY)) { rotPath[side2].fY = cubic[zero].fY; } } return true; } for (int i = 0; i < 4; ++i) { rotPath[i].fX = cubic[i].fX * dx + cubic[i].fY * dy; rotPath[i].fY = cubic[i].fY * dx - cubic[i].fX * dy; } return true; } // Returns 0 if negative, 1 if zero, 2 if positive static int side(double x) { return (x > 0) + (x >= 0); } /* Given a cubic, find the convex hull described by the end and control points. The hull may have 3 or 4 points. Cubics that degenerate into a point or line are not considered. The hull is computed by assuming that three points, if unique and non-linear, form a triangle. The fourth point may replace one of the first three, may be discarded if in the triangle or on an edge, or may be inserted between any of the three to form a convex quadralateral. The indices returned in order describe the convex hull. */ int SkDCubic::convexHull(char order[4]) const { size_t index; // find top point size_t yMin = 0; for (index = 1; index < 4; ++index) { if (fPts[yMin].fY > fPts[index].fY || (fPts[yMin].fY == fPts[index].fY && fPts[yMin].fX > fPts[index].fX)) { yMin = index; } } order[0] = yMin; int midX = -1; int backupYMin = -1; for (int pass = 0; pass < 2; ++pass) { for (index = 0; index < 4; ++index) { if (index == yMin) { continue; } // rotate line from (yMin, index) to axis // see if remaining two points are both above or below // use this to find mid int mask = other_two(yMin, index); int side1 = yMin ^ mask; int side2 = index ^ mask; SkDCubic rotPath; if (!rotate(*this, yMin, index, rotPath)) { // ! if cbc[yMin]==cbc[idx] order[1] = side1; order[2] = side2; return 3; } int sides = side(rotPath[side1].fY - rotPath[yMin].fY); sides ^= side(rotPath[side2].fY - rotPath[yMin].fY); if (sides == 2) { // '2' means one remaining point <0, one >0 if (midX >= 0) { // one of the control points is equal to an end point order[0] = 0; order[1] = 3; if (fPts[1] == fPts[0] || fPts[1] == fPts[3]) { order[2] = 2; return 3; } if (fPts[2] == fPts[0] || fPts[2] == fPts[3]) { order[2] = 1; return 3; } // one of the control points may be very nearly but not exactly equal -- double dist1_0 = fPts[1].distanceSquared(fPts[0]); double dist1_3 = fPts[1].distanceSquared(fPts[3]); double dist2_0 = fPts[2].distanceSquared(fPts[0]); double dist2_3 = fPts[2].distanceSquared(fPts[3]); double smallest1distSq = std::min(dist1_0, dist1_3); double smallest2distSq = std::min(dist2_0, dist2_3); if (approximately_zero(std::min(smallest1distSq, smallest2distSq))) { order[2] = smallest1distSq < smallest2distSq ? 2 : 1; return 3; } } midX = index; } else if (sides == 0) { // '0' means both to one side or the other backupYMin = index; } } if (midX >= 0) { break; } if (backupYMin < 0) { break; } yMin = backupYMin; backupYMin = -1; } if (midX < 0) { midX = yMin ^ 3; // choose any other point } int mask = other_two(yMin, midX); int least = yMin ^ mask; int most = midX ^ mask; order[0] = yMin; order[1] = least; // see if mid value is on same side of line (least, most) as yMin SkDCubic midPath; if (!rotate(*this, least, most, midPath)) { // ! if cbc[least]==cbc[most] order[2] = midX; return 3; } int midSides = side(midPath[yMin].fY - midPath[least].fY); midSides ^= side(midPath[midX].fY - midPath[least].fY); if (midSides != 2) { // if mid point is not between order[2] = most; return 3; // result is a triangle } order[2] = midX; order[3] = most; return 4; // result is a quadralateral } void SkOpEdgeBuilder::init() { fOperand = false; fXorMask[0] = fXorMask[1] = ((int)fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask : kWinding_PathOpsMask; fUnparseable = false; fSecondHalf = preFetch(); } // very tiny points cause numerical instability : don't allow them static SkPoint force_small_to_zero(const SkPoint& pt) { SkPoint ret = pt; if (SkScalarAbs(ret.fX) < FLT_EPSILON_ORDERABLE_ERR) { ret.fX = 0; } if (SkScalarAbs(ret.fY) < FLT_EPSILON_ORDERABLE_ERR) { ret.fY = 0; } return ret; } static bool can_add_curve(SkPath::Verb verb, SkPoint* curve) { if (SkPath::kMove_Verb == verb) { return false; } for (int index = 0; index <= SkPathOpsVerbToPoints(verb); ++index) { curve[index] = force_small_to_zero(curve[index]); } return SkPath::kLine_Verb != verb || !SkDPoint::ApproximatelyEqual(curve[0], curve[1]); } void SkOpEdgeBuilder::addOperand(const SkPath& path) { SkASSERT(!fPathVerbs.empty() && fPathVerbs.back() == SkPath::kDone_Verb); fPathVerbs.pop_back(); fPath = &path; fXorMask[1] = ((int)fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask : kWinding_PathOpsMask; preFetch(); } bool SkOpEdgeBuilder::finish() { fOperand = false; if (fUnparseable || !walk()) { return false; } complete(); SkOpContour* contour = fContourBuilder.contour(); if (contour && !contour->count()) { fContoursHead->remove(contour); } return true; } void SkOpEdgeBuilder::closeContour(const SkPoint& curveEnd, const SkPoint& curveStart) { if (!SkDPoint::ApproximatelyEqual(curveEnd, curveStart)) { *fPathVerbs.append() = SkPath::kLine_Verb; *fPathPts.append() = curveStart; } else { int verbCount = fPathVerbs.size(); int ptsCount = fPathPts.size(); if (SkPath::kLine_Verb == fPathVerbs[verbCount - 1] && fPathPts[ptsCount - 2] == curveStart) { fPathVerbs.pop_back(); fPathPts.pop_back(); } else { fPathPts[ptsCount - 1] = curveStart; } } *fPathVerbs.append() = SkPath::kClose_Verb; } int SkOpEdgeBuilder::preFetch() { if (!fPath->isFinite()) { fUnparseable = true; return 0; } SkPoint curveStart; SkPoint curve[4]; bool lastCurve = false; for (auto [pathVerb, pts, w] : SkPathPriv::Iterate(*fPath)) { auto verb = static_cast(pathVerb); switch (verb) { case SkPath::kMove_Verb: if (!fAllowOpenContours && lastCurve) { closeContour(curve[0], curveStart); } *fPathVerbs.append() = verb; curve[0] = force_small_to_zero(pts[0]); *fPathPts.append() = curve[0]; curveStart = curve[0]; lastCurve = false; continue; case SkPath::kLine_Verb: curve[1] = force_small_to_zero(pts[1]); if (SkDPoint::ApproximatelyEqual(curve[0], curve[1])) { uint8_t lastVerb = fPathVerbs.back(); if (lastVerb != SkPath::kLine_Verb && lastVerb != SkPath::kMove_Verb) { fPathPts.back() = curve[0] = curve[1]; } continue; // skip degenerate points } break; case SkPath::kQuad_Verb: curve[1] = force_small_to_zero(pts[1]); curve[2] = force_small_to_zero(pts[2]); verb = SkReduceOrder::Quad(curve, curve); if (verb == SkPath::kMove_Verb) { continue; // skip degenerate points } break; case SkPath::kConic_Verb: curve[1] = force_small_to_zero(pts[1]); curve[2] = force_small_to_zero(pts[2]); verb = SkReduceOrder::Quad(curve, curve); if (SkPath::kQuad_Verb == verb && 1 != *w) { verb = SkPath::kConic_Verb; } else if (verb == SkPath::kMove_Verb) { continue; // skip degenerate points } break; case SkPath::kCubic_Verb: curve[1] = force_small_to_zero(pts[1]); curve[2] = force_small_to_zero(pts[2]); curve[3] = force_small_to_zero(pts[3]); verb = SkReduceOrder::Cubic(curve, curve); if (verb == SkPath::kMove_Verb) { continue; // skip degenerate points } break; case SkPath::kClose_Verb: closeContour(curve[0], curveStart); lastCurve = false; continue; case SkPath::kDone_Verb: continue; } *fPathVerbs.append() = verb; int ptCount = SkPathOpsVerbToPoints(verb); fPathPts.append(ptCount, &curve[1]); if (verb == SkPath::kConic_Verb) { *fWeights.append() = *w; } curve[0] = curve[ptCount]; lastCurve = true; } if (!fAllowOpenContours && lastCurve) { closeContour(curve[0], curveStart); } *fPathVerbs.append() = SkPath::kDone_Verb; return fPathVerbs.size() - 1; } bool SkOpEdgeBuilder::close() { complete(); return true; } bool SkOpEdgeBuilder::walk() { uint8_t* verbPtr = fPathVerbs.begin(); uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf]; SkPoint* pointsPtr = fPathPts.begin(); SkScalar* weightPtr = fWeights.begin(); SkPath::Verb verb; SkOpContour* contour = fContourBuilder.contour(); int moveToPtrBump = 0; while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) { if (verbPtr == endOfFirstHalf) { fOperand = true; } verbPtr++; switch (verb) { case SkPath::kMove_Verb: if (contour && contour->count()) { if (fAllowOpenContours) { complete(); } else if (!close()) { return false; } } if (!contour) { fContourBuilder.setContour(contour = fContoursHead->appendContour()); } contour->init(fGlobalState, fOperand, fXorMask[fOperand] == kEvenOdd_PathOpsMask); pointsPtr += moveToPtrBump; moveToPtrBump = 1; continue; case SkPath::kLine_Verb: fContourBuilder.addLine(pointsPtr); break; case SkPath::kQuad_Verb: { SkVector vec1 = pointsPtr[1] - pointsPtr[0]; SkVector vec2 = pointsPtr[2] - pointsPtr[1]; if (vec1.dot(vec2) < 0) { SkPoint pair[5]; if (SkChopQuadAtMaxCurvature(pointsPtr, pair) == 1) { goto addOneQuad; } if (!SkScalarsAreFinite(&pair[0].fX, std::size(pair) * 2)) { return false; } for (unsigned index = 0; index < std::size(pair); ++index) { pair[index] = force_small_to_zero(pair[index]); } SkPoint cStorage[2][2]; SkPath::Verb v1 = SkReduceOrder::Quad(&pair[0], cStorage[0]); SkPath::Verb v2 = SkReduceOrder::Quad(&pair[2], cStorage[1]); SkPoint* curve1 = v1 != SkPath::kLine_Verb ? &pair[0] : cStorage[0]; SkPoint* curve2 = v2 != SkPath::kLine_Verb ? &pair[2] : cStorage[1]; if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { fContourBuilder.addCurve(v1, curve1); fContourBuilder.addCurve(v2, curve2); break; } } } addOneQuad: fContourBuilder.addQuad(pointsPtr); break; case SkPath::kConic_Verb: { SkVector vec1 = pointsPtr[1] - pointsPtr[0]; SkVector vec2 = pointsPtr[2] - pointsPtr[1]; SkScalar weight = *weightPtr++; if (vec1.dot(vec2) < 0) { // FIXME: max curvature for conics hasn't been implemented; use placeholder SkScalar maxCurvature = SkFindQuadMaxCurvature(pointsPtr); if (0 < maxCurvature && maxCurvature < 1) { SkConic conic(pointsPtr, weight); SkConic pair[2]; if (!conic.chopAt(maxCurvature, pair)) { // if result can't be computed, use original fContourBuilder.addConic(pointsPtr, weight); break; } SkPoint cStorage[2][3]; SkPath::Verb v1 = SkReduceOrder::Conic(pair[0], cStorage[0]); SkPath::Verb v2 = SkReduceOrder::Conic(pair[1], cStorage[1]); SkPoint* curve1 = v1 != SkPath::kLine_Verb ? pair[0].fPts : cStorage[0]; SkPoint* curve2 = v2 != SkPath::kLine_Verb ? pair[1].fPts : cStorage[1]; if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { fContourBuilder.addCurve(v1, curve1, pair[0].fW); fContourBuilder.addCurve(v2, curve2, pair[1].fW); break; } } } fContourBuilder.addConic(pointsPtr, weight); } break; case SkPath::kCubic_Verb: { // Split complex cubics (such as self-intersecting curves or // ones with difficult curvature) in two before proceeding. // This can be required for intersection to succeed. SkScalar splitT[3]; int breaks = SkDCubic::ComplexBreak(pointsPtr, splitT); if (!breaks) { fContourBuilder.addCubic(pointsPtr); break; } SkASSERT(breaks <= (int) std::size(splitT)); struct Splitsville { double fT[2]; SkPoint fPts[4]; SkPoint fReduced[4]; SkPath::Verb fVerb; bool fCanAdd; } splits[4]; SkASSERT(std::size(splits) == std::size(splitT) + 1); SkTQSort(splitT, splitT + breaks); for (int index = 0; index <= breaks; ++index) { Splitsville* split = &splits[index]; split->fT[0] = index ? splitT[index - 1] : 0; split->fT[1] = index < breaks ? splitT[index] : 1; SkDCubic part = SkDCubic::SubDivide(pointsPtr, split->fT[0], split->fT[1]); if (!part.toFloatPoints(split->fPts)) { return false; } split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced); SkPoint* curve = SkPath::kCubic_Verb == split->fVerb ? split->fPts : split->fReduced; split->fCanAdd = can_add_curve(split->fVerb, curve); } for (int index = 0; index <= breaks; ++index) { Splitsville* split = &splits[index]; if (!split->fCanAdd) { continue; } int prior = index; while (prior > 0 && !splits[prior - 1].fCanAdd) { --prior; } if (prior < index) { split->fT[0] = splits[prior].fT[0]; split->fPts[0] = splits[prior].fPts[0]; } int next = index; int breakLimit = std::min(breaks, (int) std::size(splits) - 1); while (next < breakLimit && !splits[next + 1].fCanAdd) { ++next; } if (next > index) { split->fT[1] = splits[next].fT[1]; split->fPts[3] = splits[next].fPts[3]; } if (prior < index || next > index) { split->fVerb = SkReduceOrder::Cubic(split->fPts, split->fReduced); } SkPoint* curve = SkPath::kCubic_Verb == split->fVerb ? split->fPts : split->fReduced; if (!can_add_curve(split->fVerb, curve)) { return false; } fContourBuilder.addCurve(split->fVerb, curve); } } break; case SkPath::kClose_Verb: SkASSERT(contour); if (!close()) { return false; } contour = nullptr; continue; default: SkDEBUGFAIL("bad verb"); return false; } SkASSERT(contour); if (contour->count()) { contour->debugValidate(); } pointsPtr += SkPathOpsVerbToPoints(verb); } fContourBuilder.flush(); if (contour && contour->count() &&!fAllowOpenContours && !close()) { return false; } return true; } /* After computing raw intersections, post process all segments to: - find small collections of points that can be collapsed to a single point - find missing intersections to resolve differences caused by different algorithms Consider segments containing tiny or small intervals. Consider coincident segments because coincidence finds intersections through distance measurement that non-coincident intersection tests cannot. */ #define F (false) // discard the edge #define T (true) // keep the edge static const bool gUnaryActiveEdge[2][2] = { // from=0 from=1 // to=0,1 to=0,1 {F, T}, {T, F}, }; static const bool gActiveEdge[kXOR_SkPathOp + 1][2][2][2][2] = { // miFrom=0 miFrom=1 // miTo=0 miTo=1 miTo=0 miTo=1 // suFrom=0 1 suFrom=0 1 suFrom=0 1 suFrom=0 1 // suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 {{{{F, F}, {F, F}}, {{T, F}, {T, F}}}, {{{T, T}, {F, F}}, {{F, T}, {T, F}}}}, // mi - su {{{{F, F}, {F, F}}, {{F, T}, {F, T}}}, {{{F, F}, {T, T}}, {{F, T}, {T, F}}}}, // mi & su {{{{F, T}, {T, F}}, {{T, T}, {F, F}}}, {{{T, F}, {T, F}}, {{F, F}, {F, F}}}}, // mi | su {{{{F, T}, {T, F}}, {{T, F}, {F, T}}}, {{{T, F}, {F, T}}, {{F, T}, {T, F}}}}, // mi ^ su }; #undef F #undef T SkOpAngle* SkOpSegment::activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, bool* done) { if (SkOpAngle* result = activeAngleInner(start, startPtr, endPtr, done)) { return result; } if (SkOpAngle* result = activeAngleOther(start, startPtr, endPtr, done)) { return result; } return nullptr; } SkOpAngle* SkOpSegment::activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, bool* done) { SkOpSpan* upSpan = start->upCastable(); if (upSpan) { if (upSpan->windValue() || upSpan->oppValue()) { SkOpSpanBase* next = upSpan->next(); if (!*endPtr) { *startPtr = start; *endPtr = next; } if (!upSpan->done()) { if (upSpan->windSum() != SK_MinS32) { return spanToAngle(start, next); } *done = false; } } else { SkASSERT(upSpan->done()); } } SkOpSpan* downSpan = start->prev(); // edge leading into junction if (downSpan) { if (downSpan->windValue() || downSpan->oppValue()) { if (!*endPtr) { *startPtr = start; *endPtr = downSpan; } if (!downSpan->done()) { if (downSpan->windSum() != SK_MinS32) { return spanToAngle(start, downSpan); } *done = false; } } else { SkASSERT(downSpan->done()); } } return nullptr; } SkOpAngle* SkOpSegment::activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, bool* done) { SkOpPtT* oPtT = start->ptT()->next(); SkOpSegment* other = oPtT->segment(); SkOpSpanBase* oSpan = oPtT->span(); return other->activeAngleInner(oSpan, startPtr, endPtr, done); } bool SkOpSegment::activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, SkPathOp op) { int sumMiWinding = this->updateWinding(end, start); int sumSuWinding = this->updateOppWinding(end, start); #if DEBUG_LIMIT_WIND_SUM SkASSERT(abs(sumMiWinding) <= DEBUG_LIMIT_WIND_SUM); SkASSERT(abs(sumSuWinding) <= DEBUG_LIMIT_WIND_SUM); #endif if (this->operand()) { using std::swap; swap(sumMiWinding, sumSuWinding); } return this->activeOp(xorMiMask, xorSuMask, start, end, op, &sumMiWinding, &sumSuWinding); } bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, SkPathOp op, int* sumMiWinding, int* sumSuWinding) { int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; this->setUpWindings(start, end, sumMiWinding, sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); bool miFrom; bool miTo; bool suFrom; bool suTo; if (operand()) { miFrom = (oppMaxWinding & xorMiMask) != 0; miTo = (oppSumWinding & xorMiMask) != 0; suFrom = (maxWinding & xorSuMask) != 0; suTo = (sumWinding & xorSuMask) != 0; } else { miFrom = (maxWinding & xorMiMask) != 0; miTo = (sumWinding & xorMiMask) != 0; suFrom = (oppMaxWinding & xorSuMask) != 0; suTo = (oppSumWinding & xorSuMask) != 0; } bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo]; #if DEBUG_ACTIVE_OP SkDebugf("%s id=%d t=%1.9g tEnd=%1.9g op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", __FUNCTION__, debugID(), start->t(), end->t(), SkPathOpsDebug::kPathOpStr[op], miFrom, miTo, suFrom, suTo, result); #endif return result; } bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end) { int sumWinding = updateWinding(end, start); return activeWinding(start, end, &sumWinding); } bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding) { int maxWinding; setUpWinding(start, end, &maxWinding, sumWinding); bool from = maxWinding != 0; bool to = *sumWinding != 0; bool result = gUnaryActiveEdge[from][to]; return result; } bool SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPathWriter* path) const { const SkOpSpan* spanStart = start->starter(end); FAIL_IF(spanStart->alreadyAdded()); const_cast(spanStart)->markAdded(); SkDCurveSweep curvePart; start->segment()->subDivide(start, end, &curvePart.fCurve); curvePart.setCurveHullSweep(fVerb); SkPath::Verb verb = curvePart.isCurve() ? fVerb : SkPath::kLine_Verb; path->deferredMove(start->ptT()); switch (verb) { case SkPath::kLine_Verb: FAIL_IF(!path->deferredLine(end->ptT())); break; case SkPath::kQuad_Verb: path->quadTo(curvePart.fCurve.fQuad[1].asSkPoint(), end->ptT()); break; case SkPath::kConic_Verb: path->conicTo(curvePart.fCurve.fConic[1].asSkPoint(), end->ptT(), curvePart.fCurve.fConic.fWeight); break; case SkPath::kCubic_Verb: path->cubicTo(curvePart.fCurve.fCubic[1].asSkPoint(), curvePart.fCurve.fCubic[2].asSkPoint(), end->ptT()); break; default: SkASSERT(0); } return true; } const SkOpPtT* SkOpSegment::existing(double t, const SkOpSegment* opp) const { const SkOpSpanBase* test = &fHead; const SkOpPtT* testPtT; SkPoint pt = this->ptAtT(t); do { testPtT = test->ptT(); if (testPtT->fT == t) { break; } if (!this->match(testPtT, this, t, pt)) { if (t < testPtT->fT) { return nullptr; } continue; } if (!opp) { return testPtT; } const SkOpPtT* loop = testPtT->next(); while (loop != testPtT) { if (loop->segment() == this && loop->fT == t && loop->fPt == pt) { goto foundMatch; } loop = loop->next(); } return nullptr; } while ((test = test->upCast()->next())); foundMatch: return opp && !test->contains(opp) ? nullptr : testPtT; } // break the span so that the coincident part does not change the angle of the remainder bool SkOpSegment::addExpanded(double newT, const SkOpSpanBase* test, bool* startOver) { if (this->contains(newT)) { return true; } this->globalState()->resetAllocatedOpSpan(); FAIL_IF(!between(0, newT, 1)); SkOpPtT* newPtT = this->addT(newT); *startOver |= this->globalState()->allocatedOpSpan(); if (!newPtT) { return false; } newPtT->fPt = this->ptAtT(newT); SkOpPtT* oppPrev = test->ptT()->oppPrev(newPtT); if (oppPrev) { // const cast away to change linked list; pt/t values stays unchanged SkOpSpanBase* writableTest = const_cast(test); writableTest->mergeMatches(newPtT->span()); writableTest->ptT()->addOpp(newPtT, oppPrev); writableTest->checkForCollapsedCoincidence(); } return true; } // Please keep this in sync with debugAddT() SkOpPtT* SkOpSegment::addT(double t, const SkPoint& pt) { debugValidate(); SkOpSpanBase* spanBase = &fHead; do { SkOpPtT* result = spanBase->ptT(); if (t == result->fT || (!zero_or_one(t) && this->match(result, this, t, pt))) { spanBase->bumpSpanAdds(); return result; } if (t < result->fT) { SkOpSpan* prev = result->span()->prev(); FAIL_WITH_NULL_IF(!prev); // marks in global state that new op span has been allocated SkOpSpan* span = this->insert(prev); span->init(this, prev, t, pt); this->debugValidate(); #if DEBUG_ADD_T SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, span->segment()->debugID(), span->debugID()); #endif span->bumpSpanAdds(); return span->ptT(); } FAIL_WITH_NULL_IF(spanBase == &fTail); } while ((spanBase = spanBase->upCast()->next())); SkASSERT(0); return nullptr; // we never get here, but need this to satisfy compiler } SkOpPtT* SkOpSegment::addT(double t) { return addT(t, this->ptAtT(t)); } void SkOpSegment::calcAngles() { bool activePrior = !fHead.isCanceled(); if (activePrior && !fHead.simple()) { addStartSpan(); } SkOpSpan* prior = &fHead; SkOpSpanBase* spanBase = fHead.next(); while (spanBase != &fTail) { if (activePrior) { SkOpAngle* priorAngle = this->globalState()->allocator()->make(); priorAngle->set(spanBase, prior); spanBase->setFromAngle(priorAngle); } SkOpSpan* span = spanBase->upCast(); bool active = !span->isCanceled(); SkOpSpanBase* next = span->next(); if (active) { SkOpAngle* angle = this->globalState()->allocator()->make(); angle->set(span, next); span->setToAngle(angle); } activePrior = active; prior = span; spanBase = next; } if (activePrior && !fTail.simple()) { addEndSpan(); } } // Please keep this in sync with debugClearAll() void SkOpSegment::clearAll() { SkOpSpan* span = &fHead; do { this->clearOne(span); } while ((span = span->next()->upCastable())); this->globalState()->coincidence()->release(this); } // Please keep this in sync with debugClearOne() void SkOpSegment::clearOne(SkOpSpan* span) { span->setWindValue(0); span->setOppValue(0); this->markDone(span); } SkOpSpanBase::Collapsed SkOpSegment::collapsed(double s, double e) const { const SkOpSpanBase* span = &fHead; do { SkOpSpanBase::Collapsed result = span->collapsed(s, e); if (SkOpSpanBase::Collapsed::kNo != result) { return result; } } while (span->upCastable() && (span = span->upCast()->next())); return SkOpSpanBase::Collapsed::kNo; } bool SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, SkOpAngle::IncludeType includeType) { SkOpSegment* baseSegment = baseAngle->segment(); int sumMiWinding = baseSegment->updateWindingReverse(baseAngle); int sumSuWinding; bool binary = includeType >= SkOpAngle::kBinarySingle; if (binary) { sumSuWinding = baseSegment->updateOppWindingReverse(baseAngle); if (baseSegment->operand()) { using std::swap; swap(sumMiWinding, sumSuWinding); } } SkOpSegment* nextSegment = nextAngle->segment(); int maxWinding, sumWinding; SkOpSpanBase* last = nullptr; if (binary) { int oppMaxWinding, oppSumWinding; nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, nextAngle, &last)) { return false; } } else { nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, &maxWinding, &sumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, nextAngle, &last)) { return false; } } nextAngle->setLastMarked(last); return true; } bool SkOpSegment::ComputeOneSumReverse(SkOpAngle* baseAngle, SkOpAngle* nextAngle, SkOpAngle::IncludeType includeType) { SkOpSegment* baseSegment = baseAngle->segment(); int sumMiWinding = baseSegment->updateWinding(baseAngle); int sumSuWinding; bool binary = includeType >= SkOpAngle::kBinarySingle; if (binary) { sumSuWinding = baseSegment->updateOppWinding(baseAngle); if (baseSegment->operand()) { using std::swap; swap(sumMiWinding, sumSuWinding); } } SkOpSegment* nextSegment = nextAngle->segment(); int maxWinding, sumWinding; SkOpSpanBase* last = nullptr; if (binary) { int oppMaxWinding, oppSumWinding; nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, nextAngle, &last)) { return false; } } else { nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, &maxWinding, &sumWinding); if (!nextSegment->markAngle(maxWinding, sumWinding, nextAngle, &last)) { return false; } } nextAngle->setLastMarked(last); return true; } // at this point, the span is already ordered, or unorderable int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, SkOpAngle::IncludeType includeType) { SkASSERT(includeType != SkOpAngle::kUnaryXor); SkOpAngle* firstAngle = this->spanToAngle(end, start); if (nullptr == firstAngle || nullptr == firstAngle->next()) { return SK_NaN32; } // if all angles have a computed winding, // or if no adjacent angles are orderable, // or if adjacent orderable angles have no computed winding, // there's nothing to do // if two orderable angles are adjacent, and both are next to orderable angles, // and one has winding computed, transfer to the other SkOpAngle* baseAngle = nullptr; bool tryReverse = false; // look for counterclockwise transfers SkOpAngle* angle = firstAngle->previous(); SkOpAngle* next = angle->next(); firstAngle = next; do { SkOpAngle* prior = angle; angle = next; next = angle->next(); SkASSERT(prior->next() == angle); SkASSERT(angle->next() == next); if (prior->unorderable() || angle->unorderable() || next->unorderable()) { baseAngle = nullptr; continue; } int testWinding = angle->starter()->windSum(); if (SK_MinS32 != testWinding) { baseAngle = angle; tryReverse = true; continue; } if (baseAngle) { ComputeOneSum(baseAngle, angle, includeType); baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : nullptr; } } while (next != firstAngle); if (baseAngle && SK_MinS32 == firstAngle->starter()->windSum()) { firstAngle = baseAngle; tryReverse = true; } if (tryReverse) { baseAngle = nullptr; SkOpAngle* prior = firstAngle; do { angle = prior; prior = angle->previous(); SkASSERT(prior->next() == angle); next = angle->next(); if (prior->unorderable() || angle->unorderable() || next->unorderable()) { baseAngle = nullptr; continue; } int testWinding = angle->starter()->windSum(); if (SK_MinS32 != testWinding) { baseAngle = angle; continue; } if (baseAngle) { ComputeOneSumReverse(baseAngle, angle, includeType); baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : nullptr; } } while (prior != firstAngle); } return start->starter(end)->windSum(); } bool SkOpSegment::contains(double newT) const { const SkOpSpanBase* spanBase = &fHead; do { if (spanBase->ptT()->contains(this, newT)) { return true; } if (spanBase == &fTail) { break; } spanBase = spanBase->upCast()->next(); } while (true); return false; } void SkOpSegment::release(const SkOpSpan* span) { if (span->done()) { --fDoneCount; } --fCount; SkOPASSERT(fCount >= fDoneCount); } #if DEBUG_ANGLE // called only by debugCheckNearCoincidence double SkOpSegment::distSq(double t, const SkOpAngle* oppAngle) const { SkDPoint testPt = this->dPtAtT(t); SkDLine testPerp = {{ testPt, testPt }}; SkDVector slope = this->dSlopeAtT(t); testPerp[1].fX += slope.fY; testPerp[1].fY -= slope.fX; SkIntersections i; const SkOpSegment* oppSegment = oppAngle->segment(); (*CurveIntersectRay[oppSegment->verb()])(oppSegment->pts(), oppSegment->weight(), testPerp, &i); double closestDistSq = SK_ScalarInfinity; for (int index = 0; index < i.used(); ++index) { if (!between(oppAngle->start()->t(), i[0][index], oppAngle->end()->t())) { continue; } double testDistSq = testPt.distanceSquared(i.pt(index)); if (closestDistSq > testDistSq) { closestDistSq = testDistSq; } } return closestDistSq; } #endif /* The M and S variable name parts stand for the operators. Mi stands for Minuend (see wiki subtraction, analogous to difference) Su stands for Subtrahend The Opp variable name part designates that the value is for the Opposite operator. Opposite values result from combining coincident spans. */ SkOpSegment* SkOpSegment::findNextOp(SkTDArray* chase, SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable, bool* simple, SkPathOp op, int xorMiMask, int xorSuMask) { SkOpSpanBase* start = *nextStart; SkOpSpanBase* end = *nextEnd; SkASSERT(start != end); int step = start->step(end); SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart if ((*simple = other)) { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) #if DEBUG_WINDING SkDebugf("%s simple\n", __FUNCTION__); #endif SkOpSpan* startSpan = start->starter(end); if (startSpan->done()) { return nullptr; } markDone(startSpan); *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); return other; } SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); SkASSERT(endNear == end); // is this ever not end? SkASSERT(endNear); SkASSERT(start != endNear); SkASSERT((start->t() < endNear->t()) ^ (step < 0)); // more than one viable candidate -- measure angles to find best int calcWinding = computeSum(start, endNear, SkOpAngle::kBinaryOpp); bool sortable = calcWinding != SK_NaN32; if (!sortable) { *unsortable = true; markDone(start->starter(end)); return nullptr; } SkOpAngle* angle = this->spanToAngle(end, start); if (angle->unorderable()) { *unsortable = true; markDone(start->starter(end)); return nullptr; } #if DEBUG_SORT SkDebugf("%s\n", __FUNCTION__); angle->debugLoop(); #endif int sumMiWinding = updateWinding(end, start); if (sumMiWinding == SK_MinS32) { *unsortable = true; markDone(start->starter(end)); return nullptr; } int sumSuWinding = updateOppWinding(end, start); if (operand()) { using std::swap; swap(sumMiWinding, sumSuWinding); } SkOpAngle* nextAngle = angle->next(); const SkOpAngle* foundAngle = nullptr; bool foundDone = false; // iterate through the angle, and compute everyone's winding SkOpSegment* nextSegment; int activeCount = 0; do { nextSegment = nextAngle->segment(); bool activeAngle = nextSegment->activeOp(xorMiMask, xorSuMask, nextAngle->start(), nextAngle->end(), op, &sumMiWinding, &sumSuWinding); if (activeAngle) { ++activeCount; if (!foundAngle || (foundDone && activeCount & 1)) { foundAngle = nextAngle; foundDone = nextSegment->done(nextAngle); } } if (nextSegment->done()) { continue; } if (!activeAngle) { (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end(), nullptr); } SkOpSpanBase* last = nextAngle->lastMarked(); if (last) { SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); *chase->append() = last; #if DEBUG_WINDING SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, last->segment()->debugID(), last->debugID()); if (!last->final()) { SkDebugf(" windSum=%d", last->upCast()->windSum()); } SkDebugf("\n"); #endif } } while ((nextAngle = nextAngle->next()) != angle); start->segment()->markDone(start->starter(end)); if (!foundAngle) { return nullptr; } *nextStart = foundAngle->start(); *nextEnd = foundAngle->end(); nextSegment = foundAngle->segment(); #if DEBUG_WINDING SkDebugf("%s from:[%d] to:[%d] start=%p end=%p\n", __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); #endif return nextSegment; } SkOpSegment* SkOpSegment::findNextWinding(SkTDArray* chase, SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable) { SkOpSpanBase* start = *nextStart; SkOpSpanBase* end = *nextEnd; SkASSERT(start != end); int step = start->step(end); SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart if (other) { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) #if DEBUG_WINDING SkDebugf("%s simple\n", __FUNCTION__); #endif SkOpSpan* startSpan = start->starter(end); if (startSpan->done()) { return nullptr; } markDone(startSpan); *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); return other; } SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); SkASSERT(endNear == end); // is this ever not end? SkASSERT(endNear); SkASSERT(start != endNear); SkASSERT((start->t() < endNear->t()) ^ (step < 0)); // more than one viable candidate -- measure angles to find best int calcWinding = computeSum(start, endNear, SkOpAngle::kUnaryWinding); bool sortable = calcWinding != SK_NaN32; if (!sortable) { *unsortable = true; markDone(start->starter(end)); return nullptr; } SkOpAngle* angle = this->spanToAngle(end, start); if (angle->unorderable()) { *unsortable = true; markDone(start->starter(end)); return nullptr; } #if DEBUG_SORT SkDebugf("%s\n", __FUNCTION__); angle->debugLoop(); #endif int sumWinding = updateWinding(end, start); SkOpAngle* nextAngle = angle->next(); const SkOpAngle* foundAngle = nullptr; bool foundDone = false; // iterate through the angle, and compute everyone's winding SkOpSegment* nextSegment; int activeCount = 0; do { nextSegment = nextAngle->segment(); bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(), &sumWinding); if (activeAngle) { ++activeCount; if (!foundAngle || (foundDone && activeCount & 1)) { foundAngle = nextAngle; foundDone = nextSegment->done(nextAngle); } } if (nextSegment->done()) { continue; } if (!activeAngle) { (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end(), nullptr); } SkOpSpanBase* last = nextAngle->lastMarked(); if (last) { SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); *chase->append() = last; #if DEBUG_WINDING SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, last->segment()->debugID(), last->debugID()); if (!last->final()) { SkDebugf(" windSum=%d", last->upCast()->windSum()); } SkDebugf("\n"); #endif } } while ((nextAngle = nextAngle->next()) != angle); start->segment()->markDone(start->starter(end)); if (!foundAngle) { return nullptr; } *nextStart = foundAngle->start(); *nextEnd = foundAngle->end(); nextSegment = foundAngle->segment(); #if DEBUG_WINDING SkDebugf("%s from:[%d] to:[%d] start=%p end=%p\n", __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); #endif return nextSegment; } SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable) { SkOpSpanBase* start = *nextStart; SkOpSpanBase* end = *nextEnd; SkASSERT(start != end); int step = start->step(end); SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart if (other) { // mark the smaller of startIndex, endIndex done, and all adjacent // spans with the same T value (but not 'other' spans) #if DEBUG_WINDING SkDebugf("%s simple\n", __FUNCTION__); #endif SkOpSpan* startSpan = start->starter(end); if (startSpan->done()) { return nullptr; } markDone(startSpan); *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); return other; } SkDEBUGCODE(SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() \ : (*nextStart)->prev()); SkASSERT(endNear == end); // is this ever not end? SkASSERT(endNear); SkASSERT(start != endNear); SkASSERT((start->t() < endNear->t()) ^ (step < 0)); SkOpAngle* angle = this->spanToAngle(end, start); if (!angle || angle->unorderable()) { *unsortable = true; markDone(start->starter(end)); return nullptr; } #if DEBUG_SORT SkDebugf("%s\n", __FUNCTION__); angle->debugLoop(); #endif SkOpAngle* nextAngle = angle->next(); const SkOpAngle* foundAngle = nullptr; bool foundDone = false; // iterate through the angle, and compute everyone's winding SkOpSegment* nextSegment; int activeCount = 0; do { if (!nextAngle) { return nullptr; } nextSegment = nextAngle->segment(); ++activeCount; if (!foundAngle || (foundDone && activeCount & 1)) { foundAngle = nextAngle; if (!(foundDone = nextSegment->done(nextAngle))) { break; } } nextAngle = nextAngle->next(); } while (nextAngle != angle); start->segment()->markDone(start->starter(end)); if (!foundAngle) { return nullptr; } *nextStart = foundAngle->start(); *nextEnd = foundAngle->end(); nextSegment = foundAngle->segment(); #if DEBUG_WINDING SkDebugf("%s from:[%d] to:[%d] start=%p end=%p\n", __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); #endif return nextSegment; } SkOpGlobalState* SkOpSegment::globalState() const { return contour()->globalState(); } void SkOpSegment::init(SkPoint pts[], SkScalar weight, SkOpContour* contour, SkPath::Verb verb) { fContour = contour; fNext = nullptr; fPts = pts; fWeight = weight; fVerb = verb; fCount = 0; fDoneCount = 0; fVisited = false; SkOpSpan* zeroSpan = &fHead; zeroSpan->init(this, nullptr, 0, fPts[0]); SkOpSpanBase* oneSpan = &fTail; zeroSpan->setNext(oneSpan); oneSpan->initBase(this, zeroSpan, 1, fPts[SkPathOpsVerbToPoints(fVerb)]); SkDEBUGCODE(fID = globalState()->nextSegmentID()); } bool SkOpSegment::isClose(double t, const SkOpSegment* opp) const { SkDPoint cPt = this->dPtAtT(t); SkDVector dxdy = (*CurveDSlopeAtT[this->verb()])(this->pts(), this->weight(), t); SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; SkIntersections i; (*CurveIntersectRay[opp->verb()])(opp->pts(), opp->weight(), perp, &i); int used = i.used(); for (int index = 0; index < used; ++index) { if (cPt.roughlyEqual(i.pt(index))) { return true; } } return false; } bool SkOpSegment::isXor() const { return fContour->isXor(); } void SkOpSegment::markAllDone() { SkOpSpan* span = this->head(); do { this->markDone(span); } while ((span = span->next()->upCastable())); } bool SkOpSegment::markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end, SkOpSpanBase** found) { int step = start->step(end); SkOpSpan* minSpan = start->starter(end); markDone(minSpan); SkOpSpanBase* last = nullptr; SkOpSegment* other = this; SkOpSpan* priorDone = nullptr; SkOpSpan* lastDone = nullptr; int safetyNet = 100000; while ((other = other->nextChase(&start, &step, &minSpan, &last))) { if (!--safetyNet) { return false; } if (other->done()) { SkASSERT(!last); break; } if (lastDone == minSpan || priorDone == minSpan) { if (found) { *found = nullptr; } return true; } other->markDone(minSpan); priorDone = lastDone; lastDone = minSpan; } if (found) { *found = last; } return true; } bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, SkOpSpanBase** lastPtr) { SkOpSpan* spanStart = start->starter(end); int step = start->step(end); bool success = markWinding(spanStart, winding); SkOpSpanBase* last = nullptr; SkOpSegment* other = this; int safetyNet = 100000; while ((other = other->nextChase(&start, &step, &spanStart, &last))) { if (!--safetyNet) { return false; } if (spanStart->windSum() != SK_MinS32) { // SkASSERT(spanStart->windSum() == winding); // FIXME: is this assert too aggressive? SkASSERT(!last); break; } (void) other->markWinding(spanStart, winding); } if (lastPtr) { *lastPtr = last; } return success; } bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, int oppWinding, SkOpSpanBase** lastPtr) { SkOpSpan* spanStart = start->starter(end); int step = start->step(end); bool success = markWinding(spanStart, winding, oppWinding); SkOpSpanBase* last = nullptr; SkOpSegment* other = this; int safetyNet = 100000; while ((other = other->nextChase(&start, &step, &spanStart, &last))) { if (!--safetyNet) { return false; } if (spanStart->windSum() != SK_MinS32) { if (this->operand() == other->operand()) { if (spanStart->windSum() != winding || spanStart->oppSum() != oppWinding) { this->globalState()->setWindingFailed(); return true; // ... but let it succeed anyway } } else { FAIL_IF(spanStart->windSum() != oppWinding); FAIL_IF(spanStart->oppSum() != winding); } SkASSERT(!last); break; } if (this->operand() == other->operand()) { (void) other->markWinding(spanStart, winding, oppWinding); } else { (void) other->markWinding(spanStart, oppWinding, winding); } } if (lastPtr) { *lastPtr = last; } return success; } bool SkOpSegment::markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle, SkOpSpanBase** result) { SkASSERT(angle->segment() == this); if (UseInnerWinding(maxWinding, sumWinding)) { maxWinding = sumWinding; } if (!markAndChaseWinding(angle->start(), angle->end(), maxWinding, result)) { return false; } #if DEBUG_WINDING SkOpSpanBase* last = *result; if (last) { SkDebugf("%s last seg=%d span=%d", __FUNCTION__, last->segment()->debugID(), last->debugID()); if (!last->final()) { SkDebugf(" windSum="); SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); } SkDebugf("\n"); } #endif return true; } bool SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, const SkOpAngle* angle, SkOpSpanBase** result) { SkASSERT(angle->segment() == this); if (UseInnerWinding(maxWinding, sumWinding)) { maxWinding = sumWinding; } if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) { oppMaxWinding = oppSumWinding; } // caller doesn't require that this marks anything if (!markAndChaseWinding(angle->start(), angle->end(), maxWinding, oppMaxWinding, result)) { return false; } #if DEBUG_WINDING if (result) { SkOpSpanBase* last = *result; if (last) { SkDebugf("%s last segment=%d span=%d", __FUNCTION__, last->segment()->debugID(), last->debugID()); if (!last->final()) { SkDebugf(" windSum="); SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); } SkDebugf(" \n"); } } #endif return true; } void SkOpSegment::markDone(SkOpSpan* span) { SkASSERT(this == span->segment()); if (span->done()) { return; } #if DEBUG_MARK_DONE debugShowNewWinding(__FUNCTION__, span, span->windSum(), span->oppSum()); #endif span->setDone(true); ++fDoneCount; debugValidate(); } bool SkOpSegment::markWinding(SkOpSpan* span, int winding) { SkASSERT(this == span->segment()); SkASSERT(winding); if (span->done()) { return false; } #if DEBUG_MARK_DONE debugShowNewWinding(__FUNCTION__, span, winding); #endif span->setWindSum(winding); debugValidate(); return true; } bool SkOpSegment::markWinding(SkOpSpan* span, int winding, int oppWinding) { SkASSERT(this == span->segment()); SkASSERT(winding || oppWinding); if (span->done()) { return false; } #if DEBUG_MARK_DONE debugShowNewWinding(__FUNCTION__, span, winding, oppWinding); #endif span->setWindSum(winding); span->setOppSum(oppWinding); debugValidate(); return true; } bool SkOpSegment::match(const SkOpPtT* base, const SkOpSegment* testParent, double testT, const SkPoint& testPt) const { SkASSERT(this == base->segment()); if (this == testParent) { if (precisely_equal(base->fT, testT)) { return true; } } if (!SkDPoint::ApproximatelyEqual(testPt, base->fPt)) { return false; } return this != testParent || !this->ptsDisjoint(base->fT, base->fPt, testT, testPt); } static SkOpSegment* set_last(SkOpSpanBase** last, SkOpSpanBase* endSpan) { if (last) { *last = endSpan; } return nullptr; } SkOpSegment* SkOpSegment::nextChase(SkOpSpanBase** startPtr, int* stepPtr, SkOpSpan** minPtr, SkOpSpanBase** last) const { SkOpSpanBase* origStart = *startPtr; int step = *stepPtr; SkOpSpanBase* endSpan = step > 0 ? origStart->upCast()->next() : origStart->prev(); SkASSERT(endSpan); SkOpAngle* angle = step > 0 ? endSpan->fromAngle() : endSpan->upCast()->toAngle(); SkOpSpanBase* foundSpan; SkOpSpanBase* otherEnd; SkOpSegment* other; if (angle == nullptr) { if (endSpan->t() != 0 && endSpan->t() != 1) { return nullptr; } SkOpPtT* otherPtT = endSpan->ptT()->next(); other = otherPtT->segment(); foundSpan = otherPtT->span(); otherEnd = step > 0 ? foundSpan->upCastable() ? foundSpan->upCast()->next() : nullptr : foundSpan->prev(); } else { int loopCount = angle->loopCount(); if (loopCount > 2) { return set_last(last, endSpan); } const SkOpAngle* next = angle->next(); if (nullptr == next) { return nullptr; } #if DEBUG_WINDING if (angle->debugSign() != next->debugSign() && !angle->segment()->contour()->isXor() && !next->segment()->contour()->isXor()) { SkDebugf("%s mismatched signs\n", __FUNCTION__); } #endif other = next->segment(); foundSpan = endSpan = next->start(); otherEnd = next->end(); } if (!otherEnd) { return nullptr; } int foundStep = foundSpan->step(otherEnd); if (*stepPtr != foundStep) { return set_last(last, endSpan); } SkASSERT(*startPtr); // SkASSERT(otherEnd >= 0); SkOpSpan* origMin = step < 0 ? origStart->prev() : origStart->upCast(); SkOpSpan* foundMin = foundSpan->starter(otherEnd); if (foundMin->windValue() != origMin->windValue() || foundMin->oppValue() != origMin->oppValue()) { return set_last(last, endSpan); } *startPtr = foundSpan; *stepPtr = foundStep; if (minPtr) { *minPtr = foundMin; } return other; } // Please keep this in sync with DebugClearVisited() void SkOpSegment::ClearVisited(SkOpSpanBase* span) { // reset visited flag back to false do { SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { SkOpSegment* opp = ptT->segment(); opp->resetVisited(); } } while (!span->final() && (span = span->upCast()->next())); } // Please keep this in sync with debugMissingCoincidence() // look for pairs of undetected coincident curves // assumes that segments going in have visited flag clear // Even though pairs of curves correct detect coincident runs, a run may be missed // if the coincidence is a product of multiple intersections. For instance, given // curves A, B, and C: // A-B intersect at a point 1; A-C and B-C intersect at point 2, so near // the end of C that the intersection is replaced with the end of C. // Even though A-B correctly do not detect an intersection at point 2, // the resulting run from point 1 to point 2 is coincident on A and B. bool SkOpSegment::missingCoincidence() { if (this->done()) { return false; } SkOpSpan* prior = nullptr; SkOpSpanBase* spanBase = &fHead; bool result = false; int safetyNet = 100000; do { SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; SkOPASSERT(ptT->span() == spanBase); while ((ptT = ptT->next()) != spanStopPtT) { if (!--safetyNet) { return false; } if (ptT->deleted()) { continue; } SkOpSegment* opp = ptT->span()->segment(); if (opp->done()) { continue; } // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence if (!opp->visited()) { continue; } if (spanBase == &fHead) { continue; } if (ptT->segment() == this) { continue; } SkOpSpan* span = spanBase->upCastable(); // FIXME?: this assumes that if the opposite segment is coincident then no more // coincidence needs to be detected. This may not be true. if (span && span->containsCoincidence(opp)) { continue; } if (spanBase->containsCoinEnd(opp)) { continue; } SkOpPtT* priorPtT = nullptr, * priorStopPtT; // find prior span containing opp segment SkOpSegment* priorOpp = nullptr; SkOpSpan* priorTest = spanBase->prev(); while (!priorOpp && priorTest) { priorStopPtT = priorPtT = priorTest->ptT(); while ((priorPtT = priorPtT->next()) != priorStopPtT) { if (priorPtT->deleted()) { continue; } SkOpSegment* segment = priorPtT->span()->segment(); if (segment == opp) { prior = priorTest; priorOpp = opp; break; } } priorTest = priorTest->prev(); } if (!priorOpp) { continue; } if (priorPtT == ptT) { continue; } SkOpPtT* oppStart = prior->ptT(); SkOpPtT* oppEnd = spanBase->ptT(); bool swapped = priorPtT->fT > ptT->fT; if (swapped) { using std::swap; swap(priorPtT, ptT); swap(oppStart, oppEnd); } SkOpCoincidence* coincidences = this->globalState()->coincidence(); SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); SkOpPtT* rootPtT = ptT->span()->ptT(); SkOpPtT* rootOppStart = oppStart->span()->ptT(); SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); if (coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { goto swapBack; } if (this->testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { // mark coincidence #if DEBUG_COINCIDENCE_VERBOSE SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), rootOppEnd->debugID()); #endif if (!coincidences->extend(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); } #if DEBUG_COINCIDENCE SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)); #endif result = true; } swapBack: if (swapped) { using std::swap; swap(priorPtT, ptT); } } } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); ClearVisited(&fHead); return result; } // please keep this in sync with debugMoveMultiples() // if a span has more than one intersection, merge the other segments' span as needed bool SkOpSegment::moveMultiples() { debugValidate(); SkOpSpanBase* test = &fHead; do { int addCount = test->spanAddsCount(); // FAIL_IF(addCount < 1); if (addCount <= 1) { continue; } SkOpPtT* startPtT = test->ptT(); SkOpPtT* testPtT = startPtT; int safetyHatch = 1000000; do { // iterate through all spans associated with start if (!--safetyHatch) { return false; } SkOpSpanBase* oppSpan = testPtT->span(); if (oppSpan->spanAddsCount() == addCount) { continue; } if (oppSpan->deleted()) { continue; } SkOpSegment* oppSegment = oppSpan->segment(); if (oppSegment == this) { continue; } // find range of spans to consider merging SkOpSpanBase* oppPrev = oppSpan; SkOpSpanBase* oppFirst = oppSpan; while ((oppPrev = oppPrev->prev())) { if (!roughly_equal(oppPrev->t(), oppSpan->t())) { break; } if (oppPrev->spanAddsCount() == addCount) { continue; } if (oppPrev->deleted()) { continue; } oppFirst = oppPrev; } SkOpSpanBase* oppNext = oppSpan; SkOpSpanBase* oppLast = oppSpan; while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { if (!roughly_equal(oppNext->t(), oppSpan->t())) { break; } if (oppNext->spanAddsCount() == addCount) { continue; } if (oppNext->deleted()) { continue; } oppLast = oppNext; } if (oppFirst == oppLast) { continue; } SkOpSpanBase* oppTest = oppFirst; do { if (oppTest == oppSpan) { continue; } // check to see if the candidate meets specific criteria: // it contains spans of segments in test's loop but not including 'this' SkOpPtT* oppStartPtT = oppTest->ptT(); SkOpPtT* oppPtT = oppStartPtT; while ((oppPtT = oppPtT->next()) != oppStartPtT) { SkOpSegment* oppPtTSegment = oppPtT->segment(); if (oppPtTSegment == this) { goto tryNextSpan; } SkOpPtT* matchPtT = startPtT; do { if (matchPtT->segment() == oppPtTSegment) { goto foundMatch; } } while ((matchPtT = matchPtT->next()) != startPtT); goto tryNextSpan; foundMatch: // merge oppTest and oppSpan oppSegment->debugValidate(); oppTest->mergeMatches(oppSpan); oppTest->addOpp(oppSpan); oppSegment->debugValidate(); goto checkNextSpan; } tryNextSpan: ; } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); } while ((testPtT = testPtT->next()) != startPtT); checkNextSpan: ; } while ((test = test->final() ? nullptr : test->upCast()->next())); debugValidate(); return true; } // adjacent spans may have points close by bool SkOpSegment::spansNearby(const SkOpSpanBase* refSpan, const SkOpSpanBase* checkSpan, bool* found) const { const SkOpPtT* refHead = refSpan->ptT(); const SkOpPtT* checkHead = checkSpan->ptT(); // if the first pt pair from adjacent spans are far apart, assume that all are far enough apart if (!SkDPoint::WayRoughlyEqual(refHead->fPt, checkHead->fPt)) { #if DEBUG_COINCIDENCE // verify that no combination of points are close const SkOpPtT* dBugRef = refHead; do { const SkOpPtT* dBugCheck = checkHead; do { SkOPASSERT(!SkDPoint::ApproximatelyEqual(dBugRef->fPt, dBugCheck->fPt)); dBugCheck = dBugCheck->next(); } while (dBugCheck != checkHead); dBugRef = dBugRef->next(); } while (dBugRef != refHead); #endif *found = false; return true; } // check only unique points SkScalar distSqBest = SK_ScalarMax; const SkOpPtT* refBest = nullptr; const SkOpPtT* checkBest = nullptr; const SkOpPtT* ref = refHead; do { if (ref->deleted()) { continue; } while (ref->ptAlreadySeen(refHead)) { ref = ref->next(); if (ref == refHead) { goto doneCheckingDistance; } } const SkOpPtT* check = checkHead; const SkOpSegment* refSeg = ref->segment(); int escapeHatch = 100000; // defend against infinite loops do { if (check->deleted()) { continue; } while (check->ptAlreadySeen(checkHead)) { check = check->next(); if (check == checkHead) { goto nextRef; } } SkScalar distSq = SkPointPriv::DistanceToSqd(ref->fPt, check->fPt); if (distSqBest > distSq && (refSeg != check->segment() || !refSeg->ptsDisjoint(*ref, *check))) { distSqBest = distSq; refBest = ref; checkBest = check; } if (--escapeHatch <= 0) { return false; } } while ((check = check->next()) != checkHead); nextRef: ; } while ((ref = ref->next()) != refHead); doneCheckingDistance: *found = checkBest && refBest->segment()->match(refBest, checkBest->segment(), checkBest->fT, checkBest->fPt); return true; } // Please keep this function in sync with debugMoveNearby() // Move nearby t values and pts so they all hang off the same span. Alignment happens later. bool SkOpSegment::moveNearby() { debugValidate(); // release undeleted spans pointing to this seg that are linked to the primary span SkOpSpanBase* spanBase = &fHead; int escapeHatch = 9999; // the largest count for a regular test is 50; for a fuzzer, 500 do { SkOpPtT* ptT = spanBase->ptT(); const SkOpPtT* headPtT = ptT; while ((ptT = ptT->next()) != headPtT) { if (!--escapeHatch) { return false; } SkOpSpanBase* test = ptT->span(); if (ptT->segment() == this && !ptT->deleted() && test != spanBase && test->ptT() == ptT) { if (test->final()) { if (spanBase == &fHead) { this->clearAll(); return true; } spanBase->upCast()->release(ptT); } else if (test->prev()) { test->upCast()->release(headPtT); } break; } } spanBase = spanBase->upCast()->next(); } while (!spanBase->final()); // This loop looks for adjacent spans which are near by spanBase = &fHead; do { // iterate through all spans associated with start SkOpSpanBase* test = spanBase->upCast()->next(); bool found; if (!this->spansNearby(spanBase, test, &found)) { return false; } if (found) { if (test->final()) { if (spanBase->prev()) { test->merge(spanBase->upCast()); } else { this->clearAll(); return true; } } else { spanBase->merge(test->upCast()); } } spanBase = test; } while (!spanBase->final()); debugValidate(); return true; } bool SkOpSegment::operand() const { return fContour->operand(); } bool SkOpSegment::oppXor() const { return fContour->oppXor(); } bool SkOpSegment::ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const { if (fVerb == SkPath::kLine_Verb) { return false; } // quads (and cubics) can loop back to nearly a line so that an opposite curve // hits in two places with very different t values. // OPTIMIZATION: curves could be preflighted so that, for example, something like // 'controls contained by ends' could avoid this check for common curves // 'ends are extremes in x or y' is cheaper to compute and real-world common // on the other hand, the below check is relatively inexpensive double midT = (t1 + t2) / 2; SkPoint midPt = this->ptAtT(midT); double seDistSq = std::max(SkPointPriv::DistanceToSqd(pt1, pt2) * 2, FLT_EPSILON * 2); return SkPointPriv::DistanceToSqd(midPt, pt1) > seDistSq || SkPointPriv::DistanceToSqd(midPt, pt2) > seDistSq; } void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* maxWinding, int* sumWinding) { int deltaSum = SpanSign(start, end); *maxWinding = *sumMiWinding; *sumWinding = *sumMiWinding -= deltaSum; SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); } void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) { int deltaSum = SpanSign(start, end); int oppDeltaSum = OppSign(start, end); if (operand()) { *maxWinding = *sumSuWinding; *sumWinding = *sumSuWinding -= deltaSum; *oppMaxWinding = *sumMiWinding; *oppSumWinding = *sumMiWinding -= oppDeltaSum; } else { *maxWinding = *sumMiWinding; *sumWinding = *sumMiWinding -= deltaSum; *oppMaxWinding = *sumSuWinding; *oppSumWinding = *sumSuWinding -= oppDeltaSum; } SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(*oppSumWinding) <= DEBUG_LIMIT_WIND_SUM); } bool SkOpSegment::sortAngles() { SkOpSpanBase* span = &this->fHead; do { SkOpAngle* fromAngle = span->fromAngle(); SkOpAngle* toAngle = span->final() ? nullptr : span->upCast()->toAngle(); if (!fromAngle && !toAngle) { continue; } #if DEBUG_ANGLE bool wroteAfterHeader = false; #endif SkOpAngle* baseAngle = fromAngle; if (fromAngle && toAngle) { #if DEBUG_ANGLE SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), span->t(), span->debugID()); wroteAfterHeader = true; #endif FAIL_IF(!fromAngle->insert(toAngle)); } else if (!fromAngle) { baseAngle = toAngle; } SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; int safetyNet = 1000000; do { if (!--safetyNet) { return false; } SkOpSpanBase* oSpan = ptT->span(); if (oSpan == span) { continue; } SkOpAngle* oAngle = oSpan->fromAngle(); if (oAngle) { #if DEBUG_ANGLE if (!wroteAfterHeader) { SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), span->t(), span->debugID()); wroteAfterHeader = true; } #endif if (!oAngle->loopContains(baseAngle)) { baseAngle->insert(oAngle); } } if (!oSpan->final()) { oAngle = oSpan->upCast()->toAngle(); if (oAngle) { #if DEBUG_ANGLE if (!wroteAfterHeader) { SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), span->t(), span->debugID()); wroteAfterHeader = true; } #endif if (!oAngle->loopContains(baseAngle)) { baseAngle->insert(oAngle); } } } } while ((ptT = ptT->next()) != stopPtT); if (baseAngle->loopCount() == 1) { span->setFromAngle(nullptr); if (toAngle) { span->upCast()->setToAngle(nullptr); } baseAngle = nullptr; } #if DEBUG_SORT SkASSERT(!baseAngle || baseAngle->loopCount() > 1); #endif } while (!span->final() && (span = span->upCast()->next())); return true; } bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, SkDCurve* edge) const { SkASSERT(start != end); const SkOpPtT& startPtT = *start->ptT(); const SkOpPtT& endPtT = *end->ptT(); SkDEBUGCODE(edge->fVerb = fVerb); edge->fCubic[0].set(startPtT.fPt); int points = SkPathOpsVerbToPoints(fVerb); edge->fCubic[points].set(endPtT.fPt); if (fVerb == SkPath::kLine_Verb) { return false; } double startT = startPtT.fT; double endT = endPtT.fT; if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) { // don't compute midpoints if we already have them if (fVerb == SkPath::kQuad_Verb) { edge->fLine[1].set(fPts[1]); return false; } if (fVerb == SkPath::kConic_Verb) { edge->fConic[1].set(fPts[1]); edge->fConic.fWeight = fWeight; return false; } SkASSERT(fVerb == SkPath::kCubic_Verb); if (startT == 0) { edge->fCubic[1].set(fPts[1]); edge->fCubic[2].set(fPts[2]); return false; } edge->fCubic[1].set(fPts[2]); edge->fCubic[2].set(fPts[1]); return false; } if (fVerb == SkPath::kQuad_Verb) { edge->fQuad[1] = SkDQuad::SubDivide(fPts, edge->fQuad[0], edge->fQuad[2], startT, endT); } else if (fVerb == SkPath::kConic_Verb) { edge->fConic[1] = SkDConic::SubDivide(fPts, fWeight, edge->fQuad[0], edge->fQuad[2], startT, endT, &edge->fConic.fWeight); } else { SkASSERT(fVerb == SkPath::kCubic_Verb); SkDCubic::SubDivide(fPts, edge->fCubic[0], edge->fCubic[3], startT, endT, &edge->fCubic[1]); } return true; } bool SkOpSegment::testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, const SkOpSpanBase* prior, const SkOpSpanBase* spanBase, const SkOpSegment* opp) const { // average t, find mid pt double midT = (prior->t() + spanBase->t()) / 2; SkPoint midPt = this->ptAtT(midT); bool coincident = true; // if the mid pt is not near either end pt, project perpendicular through opp seg if (!SkDPoint::ApproximatelyEqual(priorPtT->fPt, midPt) && !SkDPoint::ApproximatelyEqual(ptT->fPt, midPt)) { if (priorPtT->span() == ptT->span()) { return false; } coincident = false; SkIntersections i; SkDCurve curvePart; this->subDivide(prior, spanBase, &curvePart); SkDVector dxdy = (*CurveDDSlopeAtT[fVerb])(curvePart, 0.5f); SkDPoint partMidPt = (*CurveDDPointAtT[fVerb])(curvePart, 0.5f); SkDLine ray = {{{midPt.fX, midPt.fY}, {partMidPt.fX + dxdy.fY, partMidPt.fY - dxdy.fX}}}; SkDCurve oppPart; opp->subDivide(priorPtT->span(), ptT->span(), &oppPart); (*CurveDIntersectRay[opp->verb()])(oppPart, ray, &i); // measure distance and see if it's small enough to denote coincidence for (int index = 0; index < i.used(); ++index) { if (!between(0, i[0][index], 1)) { continue; } SkDPoint oppPt = i.pt(index); if (oppPt.approximatelyDEqual(midPt)) { // the coincidence can occur at almost any angle coincident = true; } } } return coincident; } SkOpSpan* SkOpSegment::undoneSpan() { SkOpSpan* span = &fHead; SkOpSpanBase* next; do { next = span->next(); if (!span->done()) { return span; } } while (!next->final() && (span = next->upCast())); return nullptr; } int SkOpSegment::updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const { const SkOpSpan* lesser = start->starter(end); int oppWinding = lesser->oppSum(); int oppSpanWinding = SkOpSegment::OppSign(start, end); if (oppSpanWinding && UseInnerWinding(oppWinding - oppSpanWinding, oppWinding) && oppWinding != SK_MaxS32) { oppWinding -= oppSpanWinding; } return oppWinding; } int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const { const SkOpSpanBase* startSpan = angle->start(); const SkOpSpanBase* endSpan = angle->end(); return updateOppWinding(endSpan, startSpan); } int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const { const SkOpSpanBase* startSpan = angle->start(); const SkOpSpanBase* endSpan = angle->end(); return updateOppWinding(startSpan, endSpan); } int SkOpSegment::updateWinding(SkOpSpanBase* start, SkOpSpanBase* end) { SkOpSpan* lesser = start->starter(end); int winding = lesser->windSum(); if (winding == SK_MinS32) { winding = lesser->computeWindSum(); } if (winding == SK_MinS32) { return winding; } int spanWinding = SkOpSegment::SpanSign(start, end); if (winding && UseInnerWinding(winding - spanWinding, winding) && winding != SK_MaxS32) { winding -= spanWinding; } return winding; } int SkOpSegment::updateWinding(SkOpAngle* angle) { SkOpSpanBase* startSpan = angle->start(); SkOpSpanBase* endSpan = angle->end(); return updateWinding(endSpan, startSpan); } int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) { SkOpSpanBase* startSpan = angle->start(); SkOpSpanBase* endSpan = angle->end(); return updateWinding(startSpan, endSpan); } // OPTIMIZATION: does the following also work, and is it any faster? // return outerWinding * innerWinding > 0 // || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0))) bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) { SkASSERT(outerWinding != SK_MaxS32); SkASSERT(innerWinding != SK_MaxS32); int absOut = SkTAbs(outerWinding); int absIn = SkTAbs(innerWinding); bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn; return result; } int SkOpSegment::windSum(const SkOpAngle* angle) const { const SkOpSpan* minSpan = angle->start()->starter(angle->end()); return minSpan->windSum(); } bool SkOpPtT::alias() const { return this->span()->ptT() != this; } const SkOpPtT* SkOpPtT::active() const { if (!fDeleted) { return this; } const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { if (ptT->fSpan == fSpan && !ptT->fDeleted) { return ptT; } } return nullptr; // should never return deleted; caller must abort } bool SkOpPtT::contains(const SkOpPtT* check) const { SkOPASSERT(this != check); const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { if (ptT == check) { return true; } } return false; } bool SkOpPtT::contains(const SkOpSegment* segment, const SkPoint& pt) const { SkASSERT(this->segment() != segment); const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { if (ptT->fPt == pt && ptT->segment() == segment) { return true; } } return false; } bool SkOpPtT::contains(const SkOpSegment* segment, double t) const { const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { if (ptT->fT == t && ptT->segment() == segment) { return true; } } return false; } const SkOpPtT* SkOpPtT::contains(const SkOpSegment* check) const { SkASSERT(this->segment() != check); const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { if (ptT->segment() == check && !ptT->deleted()) { return ptT; } } return nullptr; } SkOpContour* SkOpPtT::contour() const { return segment()->contour(); } const SkOpPtT* SkOpPtT::find(const SkOpSegment* segment) const { const SkOpPtT* ptT = this; const SkOpPtT* stopPtT = ptT; do { if (ptT->segment() == segment && !ptT->deleted()) { return ptT; } ptT = ptT->fNext; } while (stopPtT != ptT); // SkASSERT(0); return nullptr; } SkOpGlobalState* SkOpPtT::globalState() const { return contour()->globalState(); } void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplicate) { fT = t; fPt = pt; fSpan = span; fNext = this; fDuplicatePt = duplicate; fDeleted = false; fCoincident = false; SkDEBUGCODE(fID = span->globalState()->nextPtTID()); } bool SkOpPtT::onEnd() const { const SkOpSpanBase* span = this->span(); if (span->ptT() != this) { return false; } const SkOpSegment* segment = this->segment(); return span == segment->head() || span == segment->tail(); } bool SkOpPtT::ptAlreadySeen(const SkOpPtT* check) const { while (this != check) { if (this->fPt == check->fPt) { return true; } check = check->fNext; } return false; } SkOpPtT* SkOpPtT::prev() { SkOpPtT* result = this; SkOpPtT* next = this; while ((next = next->fNext) != this) { result = next; } SkASSERT(result->fNext == this); return result; } const SkOpSegment* SkOpPtT::segment() const { return span()->segment(); } SkOpSegment* SkOpPtT::segment() { return span()->segment(); } void SkOpPtT::setDeleted() { SkASSERT(this->span()->debugDeleted() || this->span()->ptT() != this); SkOPASSERT(!fDeleted); fDeleted = true; } bool SkOpSpanBase::addOpp(SkOpSpanBase* opp) { SkOpPtT* oppPrev = this->ptT()->oppPrev(opp->ptT()); if (!oppPrev) { return true; } FAIL_IF(!this->mergeMatches(opp)); this->ptT()->addOpp(opp->ptT(), oppPrev); this->checkForCollapsedCoincidence(); return true; } SkOpSpanBase::Collapsed SkOpSpanBase::collapsed(double s, double e) const { const SkOpPtT* start = &fPtT; const SkOpPtT* startNext = nullptr; const SkOpPtT* walk = start; double min = walk->fT; double max = min; const SkOpSegment* segment = this->segment(); int safetyNet = 100000; while ((walk = walk->next()) != start) { if (!--safetyNet) { return Collapsed::kError; } if (walk == startNext) { return Collapsed::kError; } if (walk->segment() != segment) { continue; } min = std::min(min, walk->fT); max = std::max(max, walk->fT); if (between(min, s, max) && between(min, e, max)) { return Collapsed::kYes; } startNext = start->next(); } return Collapsed::kNo; } bool SkOpSpanBase::contains(const SkOpSpanBase* span) const { const SkOpPtT* start = &fPtT; const SkOpPtT* check = &span->fPtT; SkOPASSERT(start != check); const SkOpPtT* walk = start; while ((walk = walk->next()) != start) { if (walk == check) { return true; } } return false; } const SkOpPtT* SkOpSpanBase::contains(const SkOpSegment* segment) const { const SkOpPtT* start = &fPtT; const SkOpPtT* walk = start; while ((walk = walk->next()) != start) { if (walk->deleted()) { continue; } if (walk->segment() == segment && walk->span()->ptT() == walk) { return walk; } } return nullptr; } bool SkOpSpanBase::containsCoinEnd(const SkOpSegment* segment) const { SkASSERT(this->segment() != segment); const SkOpSpanBase* next = this; while ((next = next->fCoinEnd) != this) { if (next->segment() == segment) { return true; } } return false; } SkOpContour* SkOpSpanBase::contour() const { return segment()->contour(); } SkOpGlobalState* SkOpSpanBase::globalState() const { return contour()->globalState(); } void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { fSegment = segment; fPtT.init(this, t, pt, false); fCoinEnd = this; fFromAngle = nullptr; fPrev = prev; fSpanAdds = 0; fAligned = true; fChased = false; SkDEBUGCODE(fCount = 1); SkDEBUGCODE(fID = globalState()->nextSpanID()); SkDEBUGCODE(fDebugDeleted = false); } // this pair of spans share a common t value or point; merge them and eliminate duplicates // this does not compute the best t or pt value; this merely moves all data into a single list void SkOpSpanBase::merge(SkOpSpan* span) { SkOpPtT* spanPtT = span->ptT(); SkASSERT(this->t() != spanPtT->fT); SkASSERT(!zero_or_one(spanPtT->fT)); span->release(this->ptT()); if (this->contains(span)) { SkOPASSERT(0); // check to see if this ever happens -- should have been found earlier return; // merge is already in the ptT loop } SkOpPtT* remainder = spanPtT->next(); this->ptT()->insert(spanPtT); while (remainder != spanPtT) { SkOpPtT* next = remainder->next(); SkOpPtT* compare = spanPtT->next(); while (compare != spanPtT) { SkOpPtT* nextC = compare->next(); if (nextC->span() == remainder->span() && nextC->fT == remainder->fT) { goto tryNextRemainder; } compare = nextC; } spanPtT->insert(remainder); tryNextRemainder: remainder = next; } fSpanAdds += span->fSpanAdds; } // please keep in sync with debugCheckForCollapsedCoincidence() void SkOpSpanBase::checkForCollapsedCoincidence() { SkOpCoincidence* coins = this->globalState()->coincidence(); if (coins->isEmpty()) { return; } // the insert above may have put both ends of a coincident run in the same span // for each coincident ptT in loop; see if its opposite in is also in the loop // this implementation is the motivation for marking that a ptT is referenced by a coincident span SkOpPtT* head = this->ptT(); SkOpPtT* test = head; do { if (!test->coincident()) { continue; } coins->markCollapsed(test); } while ((test = test->next()) != head); coins->releaseDeleted(); } // please keep in sync with debugMergeMatches() // Look to see if pt-t linked list contains same segment more than once // if so, and if each pt-t is directly pointed to by spans in that segment, // merge them // keep the points, but remove spans so that the segment doesn't have 2 or more // spans pointing to the same pt-t loop at different loop elements bool SkOpSpanBase::mergeMatches(SkOpSpanBase* opp) { SkOpPtT* test = &fPtT; SkOpPtT* testNext; const SkOpPtT* stop = test; int safetyHatch = 1000000; do { if (!--safetyHatch) { return false; } testNext = test->next(); if (test->deleted()) { continue; } SkOpSpanBase* testBase = test->span(); SkASSERT(testBase->ptT() == test); SkOpSegment* segment = test->segment(); if (segment->done()) { continue; } SkOpPtT* inner = opp->ptT(); const SkOpPtT* innerStop = inner; do { if (inner->segment() != segment) { continue; } if (inner->deleted()) { continue; } SkOpSpanBase* innerBase = inner->span(); SkASSERT(innerBase->ptT() == inner); // when the intersection is first detected, the span base is marked if there are // more than one point in the intersection. if (!zero_or_one(inner->fT)) { innerBase->upCast()->release(test); } else { SkOPASSERT(inner->fT != test->fT); if (!zero_or_one(test->fT)) { testBase->upCast()->release(inner); } else { segment->markAllDone(); // mark segment as collapsed SkDEBUGCODE(testBase->debugSetDeleted()); test->setDeleted(); SkDEBUGCODE(innerBase->debugSetDeleted()); inner->setDeleted(); } } #ifdef SK_DEBUG // assert if another undeleted entry points to segment const SkOpPtT* debugInner = inner; while ((debugInner = debugInner->next()) != innerStop) { if (debugInner->segment() != segment) { continue; } if (debugInner->deleted()) { continue; } SkOPASSERT(0); } #endif break; } while ((inner = inner->next()) != innerStop); } while ((test = testNext) != stop); this->checkForCollapsedCoincidence(); return true; } int SkOpSpan::computeWindSum() { SkOpGlobalState* globals = this->globalState(); SkOpContour* contourHead = globals->contourHead(); int windTry = 0; while (!this->sortableTop(contourHead) && ++windTry < SkOpGlobalState::kMaxWindingTries) { } return this->windSum(); } bool SkOpSpan::containsCoincidence(const SkOpSegment* segment) const { SkASSERT(this->segment() != segment); const SkOpSpan* next = fCoincident; do { if (next->segment() == segment) { return true; } } while ((next = next->fCoincident) != this); return false; } void SkOpSpan::init(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { SkASSERT(t != 1); initBase(segment, prev, t, pt); fCoincident = this; fToAngle = nullptr; fWindSum = fOppSum = SK_MinS32; fWindValue = 1; fOppValue = 0; fTopTTry = 0; fChased = fDone = false; segment->bumpCount(); fAlreadyAdded = false; } // Please keep this in sync with debugInsertCoincidence() bool SkOpSpan::insertCoincidence(const SkOpSegment* segment, bool flipped, bool ordered) { if (this->containsCoincidence(segment)) { return true; } SkOpPtT* next = &fPtT; while ((next = next->next()) != &fPtT) { if (next->segment() == segment) { SkOpSpan* span; SkOpSpanBase* base = next->span(); if (!ordered) { const SkOpPtT* spanEndPtT = fNext->contains(segment); FAIL_IF(!spanEndPtT); const SkOpSpanBase* spanEnd = spanEndPtT->span(); const SkOpPtT* start = base->ptT()->starter(spanEnd->ptT()); FAIL_IF(!start->span()->upCastable()); span = const_cast(start->span()->upCast()); } else if (flipped) { span = base->prev(); FAIL_IF(!span); } else { FAIL_IF(!base->upCastable()); span = base->upCast(); } this->insertCoincidence(span); return true; } } #if DEBUG_COINCIDENCE SkASSERT(0); // FIXME? if we get here, the span is missing its opposite segment... #endif return true; } void SkOpSpan::release(const SkOpPtT* kept) { SkDEBUGCODE(fDebugDeleted = true); SkOPASSERT(kept->span() != this); SkASSERT(!final()); SkOpSpan* prev = this->prev(); SkASSERT(prev); SkOpSpanBase* next = this->next(); SkASSERT(next); prev->setNext(next); next->setPrev(prev); this->segment()->release(this); SkOpCoincidence* coincidence = this->globalState()->coincidence(); if (coincidence) { coincidence->fixUp(this->ptT(), kept); } this->ptT()->setDeleted(); SkOpPtT* stopPtT = this->ptT(); SkOpPtT* testPtT = stopPtT; const SkOpSpanBase* keptSpan = kept->span(); do { if (this == testPtT->span()) { testPtT->setSpan(keptSpan); } } while ((testPtT = testPtT->next()) != stopPtT); } void SkOpSpan::setOppSum(int oppSum) { SkASSERT(!final()); if (fOppSum != SK_MinS32 && fOppSum != oppSum) { this->globalState()->setWindingFailed(); return; } SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(oppSum) <= DEBUG_LIMIT_WIND_SUM); fOppSum = oppSum; } void SkOpSpan::setWindSum(int windSum) { SkASSERT(!final()); if (fWindSum != SK_MinS32 && fWindSum != windSum) { this->globalState()->setWindingFailed(); return; } SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(windSum) <= DEBUG_LIMIT_WIND_SUM); fWindSum = windSum; } const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr, bool* sortablePtr) { // find first angle, initialize winding to computed fWindSum SkOpSegment* segment = start->segment(); const SkOpAngle* angle = segment->spanToAngle(start, end); if (!angle) { *windingPtr = SK_MinS32; return nullptr; } bool computeWinding = false; const SkOpAngle* firstAngle = angle; bool loop = false; bool unorderable = false; int winding = SK_MinS32; do { angle = angle->next(); if (!angle) { return nullptr; } unorderable |= angle->unorderable(); if ((computeWinding = unorderable || (angle == firstAngle && loop))) { break; // if we get here, there's no winding, loop is unorderable } loop |= angle == firstAngle; segment = angle->segment(); winding = segment->windSum(angle); } while (winding == SK_MinS32); // if the angle loop contains an unorderable span, the angle order may be useless // directly compute the winding in this case for each span if (computeWinding) { firstAngle = angle; winding = SK_MinS32; do { SkOpSpanBase* startSpan = angle->start(); SkOpSpanBase* endSpan = angle->end(); SkOpSpan* lesser = startSpan->starter(endSpan); int testWinding = lesser->windSum(); if (testWinding == SK_MinS32) { testWinding = lesser->computeWindSum(); } if (testWinding != SK_MinS32) { segment = angle->segment(); winding = testWinding; } angle = angle->next(); } while (angle != firstAngle); } *sortablePtr = !unorderable; *windingPtr = winding; return angle; } SkOpSpan* FindUndone(SkOpContourHead* contourHead) { SkOpContour* contour = contourHead; do { if (contour->done()) { continue; } SkOpSpan* result = contour->undoneSpan(); if (result) { return result; } } while ((contour = contour->next())); return nullptr; } SkOpSegment* FindChase(SkTDArray* chase, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr) { while (!chase->empty()) { SkOpSpanBase* span = chase->back(); chase->pop_back(); SkOpSegment* segment = span->segment(); *startPtr = span->ptT()->next()->span(); bool done = true; *endPtr = nullptr; if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done)) { *startPtr = last->start(); *endPtr = last->end(); #if TRY_ROTATE *chase->insert(0) = span; #else *chase->append() = span; #endif return last->segment(); } if (done) { continue; } // find first angle, initialize winding to computed wind sum int winding; bool sortable; const SkOpAngle* angle = AngleWinding(*startPtr, *endPtr, &winding, &sortable); if (!angle) { return nullptr; } if (winding == SK_MinS32) { continue; } int sumWinding SK_INIT_TO_AVOID_WARNING; if (sortable) { segment = angle->segment(); sumWinding = segment->updateWindingReverse(angle); } SkOpSegment* first = nullptr; const SkOpAngle* firstAngle = angle; while ((angle = angle->next()) != firstAngle) { segment = angle->segment(); SkOpSpanBase* start = angle->start(); SkOpSpanBase* end = angle->end(); int maxWinding SK_INIT_TO_AVOID_WARNING; if (sortable) { segment->setUpWinding(start, end, &maxWinding, &sumWinding); } if (!segment->done(angle)) { if (!first && (sortable || start->starter(end)->windSum() != SK_MinS32)) { first = segment; *startPtr = start; *endPtr = end; } // OPTIMIZATION: should this also add to the chase? if (sortable) { // TODO: add error handling SkAssertResult(segment->markAngle(maxWinding, sumWinding, angle, nullptr)); } } } if (first) { #if TRY_ROTATE *chase->insert(0) = span; #else *chase->append() = span; #endif return first; } } return nullptr; } bool SortContourList(SkOpContourHead** contourList, bool evenOdd, bool oppEvenOdd) { SkTDArray list; SkOpContour* contour = *contourList; do { if (contour->count()) { contour->setOppXor(contour->operand() ? evenOdd : oppEvenOdd); *list.append() = contour; } } while ((contour = contour->next())); int count = list.size(); if (!count) { return false; } if (count > 1) { SkTQSort(list.begin(), list.end()); } contour = list[0]; SkOpContourHead* contourHead = static_cast(contour); contour->globalState()->setContourHead(contourHead); *contourList = contourHead; for (int index = 1; index < count; ++index) { SkOpContour* next = list[index]; contour->setNext(next); contour = next; } contour->setNext(nullptr); return true; } static void calc_angles(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { DEBUG_STATIC_SET_PHASE(contourList); SkOpContour* contour = contourList; do { contour->calcAngles(); } while ((contour = contour->next())); } static bool missing_coincidence(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { DEBUG_STATIC_SET_PHASE(contourList); SkOpContour* contour = contourList; bool result = false; do { result |= contour->missingCoincidence(); } while ((contour = contour->next())); return result; } static bool move_multiples(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { DEBUG_STATIC_SET_PHASE(contourList); SkOpContour* contour = contourList; do { if (!contour->moveMultiples()) { return false; } } while ((contour = contour->next())); return true; } static bool move_nearby(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { DEBUG_STATIC_SET_PHASE(contourList); SkOpContour* contour = contourList; do { if (!contour->moveNearby()) { return false; } } while ((contour = contour->next())); return true; } static bool sort_angles(SkOpContourHead* contourList) { SkOpContour* contour = contourList; do { if (!contour->sortAngles()) { return false; } } while ((contour = contour->next())); return true; } bool HandleCoincidence(SkOpContourHead* contourList, SkOpCoincidence* coincidence) { SkOpGlobalState* globalState = contourList->globalState(); // match up points within the coincident runs if (!coincidence->addExpanded(DEBUG_PHASE_ONLY_PARAMS(kIntersecting))) { return false; } // combine t values when multiple intersections occur on some segments but not others if (!move_multiples(contourList DEBUG_PHASE_PARAMS(kWalking))) { return false; } // move t values and points together to eliminate small/tiny gaps if (!move_nearby(contourList DEBUG_COIN_PARAMS())) { return false; } // add coincidence formed by pairing on curve points and endpoints coincidence->correctEnds(DEBUG_PHASE_ONLY_PARAMS(kIntersecting)); if (!coincidence->addEndMovedSpans(DEBUG_COIN_ONLY_PARAMS())) { return false; } const int SAFETY_COUNT = 3; int safetyHatch = SAFETY_COUNT; // look for coincidence present in A-B and A-C but missing in B-C do { bool added; if (!coincidence->addMissing(&added DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch))) { return false; } if (!added) { break; } if (!--safetyHatch) { SkASSERT(globalState->debugSkipAssert()); return false; } move_nearby(contourList DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch - 1)); } while (true); // check to see if, loosely, coincident ranges may be expanded if (coincidence->expand(DEBUG_COIN_ONLY_PARAMS())) { bool added; if (!coincidence->addMissing(&added DEBUG_COIN_PARAMS())) { return false; } if (!coincidence->addExpanded(DEBUG_COIN_ONLY_PARAMS())) { return false; } if (!move_multiples(contourList DEBUG_COIN_PARAMS())) { return false; } move_nearby(contourList DEBUG_COIN_PARAMS()); } // the expanded ranges may not align -- add the missing spans if (!coincidence->addExpanded(DEBUG_PHASE_ONLY_PARAMS(kWalking))) { return false; } // mark spans of coincident segments as coincident coincidence->mark(DEBUG_COIN_ONLY_PARAMS()); // look for coincidence lines and curves undetected by intersection if (missing_coincidence(contourList DEBUG_COIN_PARAMS())) { (void) coincidence->expand(DEBUG_PHASE_ONLY_PARAMS(kIntersecting)); if (!coincidence->addExpanded(DEBUG_COIN_ONLY_PARAMS())) { return false; } if (!coincidence->mark(DEBUG_PHASE_ONLY_PARAMS(kWalking))) { return false; } } else { (void) coincidence->expand(DEBUG_COIN_ONLY_PARAMS()); } (void) coincidence->expand(DEBUG_COIN_ONLY_PARAMS()); SkOpCoincidence overlaps(globalState); safetyHatch = SAFETY_COUNT; do { SkOpCoincidence* pairs = overlaps.isEmpty() ? coincidence : &overlaps; // adjust the winding value to account for coincident edges if (!pairs->apply(DEBUG_ITER_ONLY_PARAMS(SAFETY_COUNT - safetyHatch))) { return false; } // For each coincident pair that overlaps another, when the receivers (the 1st of the pair) // are different, construct a new pair to resolve their mutual span if (!pairs->findOverlaps(&overlaps DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch))) { return false; } if (!--safetyHatch) { SkASSERT(globalState->debugSkipAssert()); return false; } } while (!overlaps.isEmpty()); calc_angles(contourList DEBUG_COIN_PARAMS()); if (!sort_angles(contourList)) { return false; } #if DEBUG_COINCIDENCE_VERBOSE coincidence->debugShowCoincidence(); #endif #if DEBUG_COINCIDENCE coincidence->debugValidate(); #endif SkPathOpsDebug::ShowActiveSpans(contourList); return true; } struct SkDLine; // cribbed from the float version in SkGeometry.cpp static void conic_deriv_coeff(const double src[], SkScalar w, double coeff[3]) { const double P20 = src[4] - src[0]; const double P10 = src[2] - src[0]; const double wP10 = w * P10; coeff[0] = w * P20 - P20; coeff[1] = P20 - 2 * wP10; coeff[2] = wP10; } static double conic_eval_tan(const double coord[], SkScalar w, double t) { double coeff[3]; conic_deriv_coeff(coord, w, coeff); return t * (t * coeff[0] + coeff[1]) + coeff[2]; } int SkDConic::FindExtrema(const double src[], SkScalar w, double t[1]) { double coeff[3]; conic_deriv_coeff(src, w, coeff); double tValues[2]; int roots = SkDQuad::RootsValidT(coeff[0], coeff[1], coeff[2], tValues); // In extreme cases, the number of roots returned can be 2. Pathops // will fail later on, so there's no advantage to plumbing in an error // return here. // SkASSERT(0 == roots || 1 == roots); if (1 == roots) { t[0] = tValues[0]; return 1; } return 0; } SkDVector SkDConic::dxdyAtT(double t) const { SkDVector result = { conic_eval_tan(&fPts[0].fX, fWeight, t), conic_eval_tan(&fPts[0].fY, fWeight, t) }; if (result.fX == 0 && result.fY == 0) { if (zero_or_one(t)) { result = fPts[2] - fPts[0]; } else { // incomplete SkDebugf("!k"); } } return result; } static double conic_eval_numerator(const double src[], SkScalar w, double t) { SkASSERT(src); SkASSERT(t >= 0 && t <= 1); double src2w = src[2] * w; double C = src[0]; double A = src[4] - 2 * src2w + C; double B = 2 * (src2w - C); return (A * t + B) * t + C; } static double conic_eval_denominator(SkScalar w, double t) { double B = 2 * (w - 1); double C = 1; double A = -B; return (A * t + B) * t + C; } bool SkDConic::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { return cubic.hullIntersects(*this, isLinear); } SkDPoint SkDConic::ptAtT(double t) const { if (t == 0) { return fPts[0]; } if (t == 1) { return fPts[2]; } double denominator = conic_eval_denominator(fWeight, t); SkDPoint result = { sk_ieee_double_divide(conic_eval_numerator(&fPts[0].fX, fWeight, t), denominator), sk_ieee_double_divide(conic_eval_numerator(&fPts[0].fY, fWeight, t), denominator) }; return result; } /* see quad subdivide for point rationale */ /* w rationale : the mid point between t1 and t2 could be determined from the computed a/b/c values if the computed w was known. Since we know the mid point at (t1+t2)/2, we'll assume that it is the same as the point on the new curve t==(0+1)/2. d / dz == conic_poly(dst, unknownW, .5) / conic_weight(unknownW, .5); conic_poly(dst, unknownW, .5) = a / 4 + (b * unknownW) / 2 + c / 4 = (a + c) / 4 + (bx * unknownW) / 2 conic_weight(unknownW, .5) = unknownW / 2 + 1 / 2 d / dz == ((a + c) / 2 + b * unknownW) / (unknownW + 1) d / dz * (unknownW + 1) == (a + c) / 2 + b * unknownW unknownW = ((a + c) / 2 - d / dz) / (d / dz - b) Thus, w is the ratio of the distance from the mid of end points to the on-curve point, and the distance of the on-curve point to the control point. */ SkDConic SkDConic::subDivide(double t1, double t2) const { double ax, ay, az; if (t1 == 0) { ax = fPts[0].fX; ay = fPts[0].fY; az = 1; } else if (t1 != 1) { ax = conic_eval_numerator(&fPts[0].fX, fWeight, t1); ay = conic_eval_numerator(&fPts[0].fY, fWeight, t1); az = conic_eval_denominator(fWeight, t1); } else { ax = fPts[2].fX; ay = fPts[2].fY; az = 1; } double midT = (t1 + t2) / 2; double dx = conic_eval_numerator(&fPts[0].fX, fWeight, midT); double dy = conic_eval_numerator(&fPts[0].fY, fWeight, midT); double dz = conic_eval_denominator(fWeight, midT); double cx, cy, cz; if (t2 == 1) { cx = fPts[2].fX; cy = fPts[2].fY; cz = 1; } else if (t2 != 0) { cx = conic_eval_numerator(&fPts[0].fX, fWeight, t2); cy = conic_eval_numerator(&fPts[0].fY, fWeight, t2); cz = conic_eval_denominator(fWeight, t2); } else { cx = fPts[0].fX; cy = fPts[0].fY; cz = 1; } double bx = 2 * dx - (ax + cx) / 2; double by = 2 * dy - (ay + cy) / 2; double bz = 2 * dz - (az + cz) / 2; if (!bz) { bz = 1; // if bz is 0, weight is 0, control point has no effect: any value will do } SkDConic dst = {{{{ax / az, ay / az}, {bx / bz, by / bz}, {cx / cz, cy / cz}} SkDEBUGPARAMS(fPts.fDebugGlobalState) }, SkDoubleToScalar(bz / sqrt(az * cz)) }; return dst; } SkDPoint SkDConic::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2, SkScalar* weight) const { SkDConic chopped = this->subDivide(t1, t2); *weight = chopped.fWeight; return chopped[1]; } int SkTConic::intersectRay(SkIntersections* i, const SkDLine& line) const { return i->intersectRay(fConic, line); } bool SkTConic::hullIntersects(const SkDQuad& quad, bool* isLinear) const { return quad.hullIntersects(fConic, isLinear); } bool SkTConic::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { return cubic.hullIntersects(fConic, isLinear); } void SkTConic::setBounds(SkDRect* rect) const { rect->setBounds(fConic); } struct SkDLine; const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const { if (fPts[endIndex].fX == fPts[ctrlIndex].fX) { dstPt->fX = fPts[endIndex].fX; } if (fPts[endIndex].fY == fPts[ctrlIndex].fY) { dstPt->fY = fPts[endIndex].fY; } } // give up when changing t no longer moves point // also, copy point rather than recompute it when it does change double SkDCubic::binarySearch(double min, double max, double axisIntercept, SearchAxis xAxis) const { double t = (min + max) / 2; double step = (t - min) / 2; SkDPoint cubicAtT = ptAtT(t); double calcPos = (&cubicAtT.fX)[xAxis]; double calcDist = calcPos - axisIntercept; do { double priorT = std::max(min, t - step); SkDPoint lessPt = ptAtT(priorT); if (approximately_equal_half(lessPt.fX, cubicAtT.fX) && approximately_equal_half(lessPt.fY, cubicAtT.fY)) { return -1; // binary search found no point at this axis intercept } double lessDist = (&lessPt.fX)[xAxis] - axisIntercept; #if DEBUG_CUBIC_BINARY_SEARCH SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist, step, lessDist); #endif double lastStep = step; step /= 2; if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) { t = priorT; } else { double nextT = t + lastStep; if (nextT > max) { return -1; } SkDPoint morePt = ptAtT(nextT); if (approximately_equal_half(morePt.fX, cubicAtT.fX) && approximately_equal_half(morePt.fY, cubicAtT.fY)) { return -1; // binary search found no point at this axis intercept } double moreDist = (&morePt.fX)[xAxis] - axisIntercept; if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) { continue; } t = nextT; } SkDPoint testAtT = ptAtT(t); cubicAtT = testAtT; calcPos = (&cubicAtT.fX)[xAxis]; calcDist = calcPos - axisIntercept; } while (!approximately_equal(calcPos, axisIntercept)); return t; } // get the rough scale of the cubic; used to determine if curvature is extreme double SkDCubic::calcPrecision() const { return ((fPts[1] - fPts[0]).length() + (fPts[2] - fPts[1]).length() + (fPts[3] - fPts[2]).length()) / gPrecisionUnit; } /* classic one t subdivision */ static void interp_cubic_coords(const double* src, double* dst, double t) { double ab = SkDInterp(src[0], src[2], t); double bc = SkDInterp(src[2], src[4], t); double cd = SkDInterp(src[4], src[6], t); double abc = SkDInterp(ab, bc, t); double bcd = SkDInterp(bc, cd, t); double abcd = SkDInterp(abc, bcd, t); dst[0] = src[0]; dst[2] = ab; dst[4] = abc; dst[6] = abcd; dst[8] = bcd; dst[10] = cd; dst[12] = src[6]; } SkDCubicPair SkDCubic::chopAt(double t) const { SkDCubicPair dst; if (t == 0.5) { dst.pts[0] = fPts[0]; dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2; dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2; dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4; dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4; dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8; dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8; dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4; dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4; dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2; dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2; dst.pts[6] = fPts[3]; return dst; } interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t); interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t); return dst; } // TODO(skbug.com/14063) deduplicate this with SkBezierCubic::ConvertToPolynomial void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) { *A = src[6]; // d *B = src[4] * 3; // 3*c *C = src[2] * 3; // 3*b *D = src[0]; // a *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c *C -= 3 * *D; // C = -3*a + 3*b } bool SkDCubic::endsAreExtremaInXOrY() const { return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX) && between(fPts[0].fX, fPts[2].fX, fPts[3].fX)) || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY) && between(fPts[0].fY, fPts[2].fY, fPts[3].fY)); } // Do a quick reject by rotating all points relative to a line formed by // a pair of one cubic's points. If the 2nd cubic's points // are on the line or on the opposite side from the 1st cubic's 'odd man', the // curves at most intersect at the endpoints. /* if returning true, check contains true if cubic's hull collapsed, making the cubic linear if returning false, check contains true if the the cubic pair have only the end point in common */ bool SkDCubic::hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const { bool linear = true; char hullOrder[4]; int hullCount = convexHull(hullOrder); int end1 = hullOrder[0]; int hullIndex = 0; const SkDPoint* endPt[2]; endPt[0] = &fPts[end1]; do { hullIndex = (hullIndex + 1) % hullCount; int end2 = hullOrder[hullIndex]; endPt[1] = &fPts[end2]; double origX = endPt[0]->fX; double origY = endPt[0]->fY; double adj = endPt[1]->fX - origX; double opp = endPt[1]->fY - origY; int oddManMask = other_two(end1, end2); int oddMan = end1 ^ oddManMask; double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; int oddMan2 = end2 ^ oddManMask; double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp; if (sign * sign2 < 0) { continue; } if (approximately_zero(sign)) { sign = sign2; if (approximately_zero(sign)) { continue; } } linear = false; bool foundOutlier = false; for (int n = 0; n < ptCount; ++n) { double test = (pts[n].fY - origY) * adj - (pts[n].fX - origX) * opp; if (test * sign > 0 && !precisely_zero(test)) { foundOutlier = true; break; } } if (!foundOutlier) { return false; } endPt[0] = endPt[1]; end1 = end2; } while (hullIndex); *isLinear = linear; return true; } bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const { return hullIntersects(c2.fPts, SkDCubic::kPointCount, isLinear); } bool SkDCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const { return hullIntersects(quad.fPts, SkDQuad::kPointCount, isLinear); } bool SkDCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const { return hullIntersects(conic.fPts, isLinear); } bool SkDCubic::isLinear(int startIndex, int endIndex) const { if (fPts[0].approximatelyDEqual(fPts[3])) { return ((const SkDQuad *) this)->isLinear(0, 2); } SkLineParameters lineParameters; lineParameters.cubicEndPoints(*this, startIndex, endIndex); // FIXME: maybe it's possible to avoid this and compare non-normalized lineParameters.normalize(); double tiniest = std::min(std::min(std::min(std::min(std::min(std::min(std::min(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); double largest = std::max(std::max(std::max(std::max(std::max(std::max(std::max(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); largest = std::max(largest, -tiniest); double distance = lineParameters.controlPtDistance(*this, 1); if (!approximately_zero_when_compared_to(distance, largest)) { return false; } distance = lineParameters.controlPtDistance(*this, 2); return approximately_zero_when_compared_to(distance, largest); } // from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf // c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3 // c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2 // = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2 static double derivative_at_t(const double* src, double t) { double one_t = 1 - t; double a = src[0]; double b = src[2]; double c = src[4]; double d = src[6]; return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); } int SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { SkDCubic cubic; cubic.set(pointsPtr); if (cubic.monotonicInX() && cubic.monotonicInY()) { return 0; } double tt[2], ss[2]; SkCubicType cubicType = SkClassifyCubic(pointsPtr, tt, ss); switch (cubicType) { case SkCubicType::kLoop: { const double &td = tt[0], &te = tt[1], &sd = ss[0], &se = ss[1]; if (roughly_between(0, td, sd) && roughly_between(0, te, se)) { t[0] = static_cast((td * se + te * sd) / (2 * sd * se)); return (int) (t[0] > 0 && t[0] < 1); } } [[fallthrough]]; // fall through if no t value found case SkCubicType::kSerpentine: case SkCubicType::kLocalCusp: case SkCubicType::kCuspAtInfinity: { double inflectionTs[2]; int infTCount = cubic.findInflections(inflectionTs); double maxCurvature[3]; int roots = cubic.findMaxCurvature(maxCurvature); #if DEBUG_CUBIC_SPLIT SkDebugf("%s\n", __FUNCTION__); cubic.dump(); for (int index = 0; index < infTCount; ++index) { SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]); SkDPoint pt = cubic.ptAtT(inflectionTs[index]); SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } for (int index = 0; index < roots; ++index) { SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]); SkDPoint pt = cubic.ptAtT(maxCurvature[index]); SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]); SkDLine perp = {{pt - dPt, pt + dPt}}; perp.dump(); } #endif if (infTCount == 2) { for (int index = 0; index < roots; ++index) { if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { t[0] = maxCurvature[index]; return (int) (t[0] > 0 && t[0] < 1); } } } else { int resultCount = 0; // FIXME: constant found through experimentation -- maybe there's a better way.... double precision = cubic.calcPrecision() * 2; for (int index = 0; index < roots; ++index) { double testT = maxCurvature[index]; if (0 >= testT || testT >= 1) { continue; } // don't call dxdyAtT since we want (0,0) results SkDVector dPt = { derivative_at_t(&cubic.fPts[0].fX, testT), derivative_at_t(&cubic.fPts[0].fY, testT) }; double dPtLen = dPt.length(); if (dPtLen < precision) { t[resultCount++] = testT; } } if (!resultCount && infTCount == 1) { t[0] = inflectionTs[0]; resultCount = (int) (t[0] > 0 && t[0] < 1); } return resultCount; } break; } default: break; } return 0; } bool SkDCubic::monotonicInX() const { return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX) && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX); } bool SkDCubic::monotonicInY() const { return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY) && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY); } void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const { int offset = (int) !SkToBool(index); o1Pts[0] = &fPts[offset]; o1Pts[1] = &fPts[++offset]; o1Pts[2] = &fPts[++offset]; } int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept, SearchAxis xAxis, double* validRoots) const { extrema += findInflections(&extremeTs[extrema]); extremeTs[extrema++] = 0; extremeTs[extrema] = 1; SkASSERT(extrema < 6); SkTQSort(extremeTs, extremeTs + extrema + 1); int validCount = 0; for (int index = 0; index < extrema; ) { double min = extremeTs[index]; double max = extremeTs[++index]; if (min == max) { continue; } double newT = binarySearch(min, max, axisIntercept, xAxis); if (newT >= 0) { if (validCount >= 3) { return 0; } validRoots[validCount++] = newT; } } return validCount; } // cubic roots static const double skPathOpsCubic_PI = 3.141592653589793; // from SkGeometry.cpp (and Numeric Solutions, 5.6) // // TODO(skbug.com/14063) Deduplicate with SkCubics::RootsValidT int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) { double s[3]; int realRoots = RootsReal(A, B, C, D, s); int foundRoots = SkDQuad::AddValidTs(s, realRoots, t); for (int index = 0; index < realRoots; ++index) { double tValue = s[index]; if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) { for (int idx2 = 0; idx2 < foundRoots; ++idx2) { if (approximately_equal(t[idx2], 1)) { goto nextRoot; } } SkASSERT(foundRoots < 3); t[foundRoots++] = 1; } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) { for (int idx2 = 0; idx2 < foundRoots; ++idx2) { if (approximately_equal(t[idx2], 0)) { goto nextRoot; } } SkASSERT(foundRoots < 3); t[foundRoots++] = 0; } nextRoot: ; } return foundRoots; } // TODO(skbug.com/14063) Deduplicate with SkCubics::RootsReal int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) { #ifdef SK_DEBUG #if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA // create a string mathematica understands // GDB set print repe 15 # if repeated digits is a bother // set print elements 400 # if line doesn't fit char str[1024]; sk_bzero(str, sizeof(str)); snprintf(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]", A, B, C, D); SkPathOpsDebug::MathematicaIze(str, sizeof(str)); SkDebugf("%s\n", str); #endif #endif if (approximately_zero(A) && approximately_zero_when_compared_to(A, B) && approximately_zero_when_compared_to(A, C) && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic return SkDQuad::RootsReal(B, C, D, s); } if (approximately_zero_when_compared_to(D, A) && approximately_zero_when_compared_to(D, B) && approximately_zero_when_compared_to(D, C)) { // 0 is one root int num = SkDQuad::RootsReal(A, B, C, s); for (int i = 0; i < num; ++i) { if (approximately_zero(s[i])) { return num; } } s[num++] = 0; return num; } if (approximately_zero(A + B + C + D)) { // 1 is one root int num = SkDQuad::RootsReal(A, A + B, -D, s); for (int i = 0; i < num; ++i) { if (AlmostDequalUlps(s[i], 1)) { return num; } } s[num++] = 1; return num; } double a, b, c; { double invA = 1 / A; a = B * invA; b = C * invA; c = D * invA; } double a2 = a * a; double Q = (a2 - b * 3) / 9; double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; double R2 = R * R; double Q3 = Q * Q * Q; double R2MinusQ3 = R2 - Q3; double adiv3 = a / 3; double r; double* roots = s; if (R2MinusQ3 < 0) { // we have 3 real roots // the divide/root can, due to finite precisions, be slightly outside of -1...1 double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.)); double neg2RootQ = -2 * sqrt(Q); r = neg2RootQ * cos(theta / 3) - adiv3; *roots++ = r; r = neg2RootQ * cos((theta + 2 * skPathOpsCubic_PI) / 3) - adiv3; if (!AlmostDequalUlps(s[0], r)) { *roots++ = r; } r = neg2RootQ * cos((theta - 2 * skPathOpsCubic_PI) / 3) - adiv3; if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) { *roots++ = r; } } else { // we have 1 real root double sqrtR2MinusQ3 = sqrt(R2MinusQ3); A = fabs(R) + sqrtR2MinusQ3; A = std::cbrt(A); // cube root if (R > 0) { A = -A; } if (A != 0) { A += Q / A; } r = A - adiv3; *roots++ = r; if (AlmostDequalUlps((double) R2, (double) Q3)) { r = -A / 2 - adiv3; if (!AlmostDequalUlps(s[0], r)) { *roots++ = r; } } } return static_cast(roots - s); } // OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t? SkDVector SkDCubic::dxdyAtT(double t) const { SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) }; if (result.fX == 0 && result.fY == 0) { if (t == 0) { result = fPts[2] - fPts[0]; } else if (t == 1) { result = fPts[3] - fPts[1]; } else { // incomplete SkDebugf("!c"); } if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) { result = fPts[3] - fPts[0]; } } return result; } // OPTIMIZE? share code with formulate_F1DotF2 // e.g. https://stackoverflow.com/a/35927917 int SkDCubic::findInflections(double tValues[2]) const { double Ax = fPts[1].fX - fPts[0].fX; double Ay = fPts[1].fY - fPts[0].fY; double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX; double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY; double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX; double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY; return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues); } static void formulate_F1DotF2(const double src[], double coeff[4]) { double a = src[2] - src[0]; double b = src[4] - 2 * src[2] + src[0]; double c = src[6] + 3 * (src[2] - src[4]) - src[0]; coeff[0] = c * c; coeff[1] = 3 * b * c; coeff[2] = 2 * b * b + c * a; coeff[3] = a * b; } /** SkDCubic'(t) = At^2 + Bt + C, where A = 3(-a + 3(b - c) + d) B = 6(a - 2b + c) C = 3(b - a) Solve for t, keeping only those that fit between 0 < t < 1 */ int SkDCubic::FindExtrema(const double src[], double tValues[2]) { // we divide A,B,C by 3 to simplify double a = src[0]; double b = src[2]; double c = src[4]; double d = src[6]; double A = d - a + 3 * (b - c); double B = 2 * (a - b - b + c); double C = b - a; return SkDQuad::RootsValidT(A, B, C, tValues); } /* from SkGeometry.cpp Looking for F' dot F'' == 0 A = b - a B = c - 2b + a C = d - 3c + 3b - a F' = 3Ct^2 + 6Bt + 3A F'' = 6Ct + 6B F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB */ int SkDCubic::findMaxCurvature(double tValues[]) const { double coeffX[4], coeffY[4]; int i; formulate_F1DotF2(&fPts[0].fX, coeffX); formulate_F1DotF2(&fPts[0].fY, coeffY); for (i = 0; i < 4; i++) { coeffX[i] = coeffX[i] + coeffY[i]; } return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues); } SkDPoint SkDCubic::ptAtT(double t) const { if (0 == t) { return fPts[0]; } if (1 == t) { return fPts[3]; } double one_t = 1 - t; double one_t2 = one_t * one_t; double a = one_t2 * one_t; double b = 3 * one_t2 * t; double t2 = t * t; double c = 3 * one_t * t2; double d = t2 * t; SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX, a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY}; return result; } /* Given a cubic c, t1, and t2, find a small cubic segment. The new cubic is defined as points A, B, C, and D, where s1 = 1 - t1 s2 = 1 - t2 A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1 D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2 We don't have B or C. So We define two equations to isolate them. First, compute two reference T values 1/3 and 2/3 from t1 to t2: c(at (2*t1 + t2)/3) == E c(at (t1 + 2*t2)/3) == F Next, compute where those values must be if we know the values of B and C: _12 = A*2/3 + B*1/3 12_ = A*1/3 + B*2/3 _23 = B*2/3 + C*1/3 23_ = B*1/3 + C*2/3 _34 = C*2/3 + D*1/3 34_ = C*1/3 + D*2/3 _123 = (A*2/3 + B*1/3)*2/3 + (B*2/3 + C*1/3)*1/3 = A*4/9 + B*4/9 + C*1/9 123_ = (A*1/3 + B*2/3)*1/3 + (B*1/3 + C*2/3)*2/3 = A*1/9 + B*4/9 + C*4/9 _234 = (B*2/3 + C*1/3)*2/3 + (C*2/3 + D*1/3)*1/3 = B*4/9 + C*4/9 + D*1/9 234_ = (B*1/3 + C*2/3)*1/3 + (C*1/3 + D*2/3)*2/3 = B*1/9 + C*4/9 + D*4/9 _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3 = A*8/27 + B*12/27 + C*6/27 + D*1/27 = E 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3 = A*1/27 + B*6/27 + C*12/27 + D*8/27 = F E*27 = A*8 + B*12 + C*6 + D F*27 = A + B*6 + C*12 + D*8 Group the known values on one side: M = E*27 - A*8 - D = B*12 + C* 6 N = F*27 - A - D*8 = B* 6 + C*12 M*2 - N = B*18 N*2 - M = C*18 B = (M*2 - N)/18 C = (N*2 - M)/18 */ static double interp_cubic_coords(const double* src, double t) { double ab = SkDInterp(src[0], src[2], t); double bc = SkDInterp(src[2], src[4], t); double cd = SkDInterp(src[4], src[6], t); double abc = SkDInterp(ab, bc, t); double bcd = SkDInterp(bc, cd, t); double abcd = SkDInterp(abc, bcd, t); return abcd; } SkDCubic SkDCubic::subDivide(double t1, double t2) const { if (t1 == 0 || t2 == 1) { if (t1 == 0 && t2 == 1) { return *this; } SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1); SkDCubic dst = t1 == 0 ? pair.first() : pair.second(); return dst; } SkDCubic dst; double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1); double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1); double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3); double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3); double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3); double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3); double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2); double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2); double mx = ex * 27 - ax * 8 - dx; double my = ey * 27 - ay * 8 - dy; double nx = fx * 27 - ax - dx * 8; double ny = fy * 27 - ay - dy * 8; /* bx = */ dst[1].fX = (mx * 2 - nx) / 18; /* by = */ dst[1].fY = (my * 2 - ny) / 18; /* cx = */ dst[2].fX = (nx * 2 - mx) / 18; /* cy = */ dst[2].fY = (ny * 2 - my) / 18; // FIXME: call align() ? return dst; } void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint dst[2]) const { SkASSERT(t1 != t2); // this approach assumes that the control points computed directly are accurate enough SkDCubic sub = subDivide(t1, t2); dst[0] = sub[1] + (a - sub[0]); dst[1] = sub[2] + (d - sub[3]); if (t1 == 0 || t2 == 0) { align(0, 1, t1 == 0 ? &dst[0] : &dst[1]); } if (t1 == 1 || t2 == 1) { align(3, 2, t1 == 1 ? &dst[0] : &dst[1]); } if (AlmostBequalUlps(dst[0].fX, a.fX)) { dst[0].fX = a.fX; } if (AlmostBequalUlps(dst[0].fY, a.fY)) { dst[0].fY = a.fY; } if (AlmostBequalUlps(dst[1].fX, d.fX)) { dst[1].fX = d.fX; } if (AlmostBequalUlps(dst[1].fY, d.fY)) { dst[1].fY = d.fY; } } bool SkDCubic::toFloatPoints(SkPoint* pts) const { const double* dCubic = &fPts[0].fX; SkScalar* cubic = &pts[0].fX; for (int index = 0; index < kPointCount * 2; ++index) { cubic[index] = SkDoubleToScalar(dCubic[index]); if (SkScalarAbs(cubic[index]) < FLT_EPSILON_ORDERABLE_ERR) { cubic[index] = 0; } } return SkScalarsAreFinite(&pts->fX, kPointCount * 2); } double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const { double extremeTs[2]; double topT = -1; int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs); for (int index = 0; index < roots; ++index) { double t = startT + (endT - startT) * extremeTs[index]; SkDPoint mid = dCurve.ptAtT(t); if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) { topT = t; *topPt = mid; } } return topT; } int SkTCubic::intersectRay(SkIntersections* i, const SkDLine& line) const { return i->intersectRay(fCubic, line); } bool SkTCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const { return quad.hullIntersects(fCubic, isLinear); } bool SkTCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const { return conic.hullIntersects(fCubic, isLinear); } void SkTCubic::setBounds(SkDRect* rect) const { rect->setBounds(fCubic); } // this cheats and assumes that the perpendicular to the point is the closest ray to the curve // this case (where the line and the curve are nearly coincident) may be the only case that counts double SkDCurve::nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const { int count = SkPathOpsVerbToPoints(verb); double minX = fCubic.fPts[0].fX; double maxX = minX; for (int index = 1; index <= count; ++index) { minX = std::min(minX, fCubic.fPts[index].fX); maxX = std::max(maxX, fCubic.fPts[index].fX); } if (!AlmostBetweenUlps(minX, xy.fX, maxX)) { return -1; } double minY = fCubic.fPts[0].fY; double maxY = minY; for (int index = 1; index <= count; ++index) { minY = std::min(minY, fCubic.fPts[index].fY); maxY = std::max(maxY, fCubic.fPts[index].fY); } if (!AlmostBetweenUlps(minY, xy.fY, maxY)) { return -1; } SkIntersections i; SkDLine perp = {{ xy, { xy.fX + opp.fY - xy.fY, xy.fY + xy.fX - opp.fX }}}; (*CurveDIntersectRay[verb])(*this, perp, &i); int minIndex = -1; double minDist = FLT_MAX; for (int index = 0; index < i.used(); ++index) { double dist = xy.distance(i.pt(index)); if (minDist > dist) { minDist = dist; minIndex = index; } } if (minIndex < 0) { return -1; } double largest = std::max(std::max(maxX, maxY), -std::min(minX, minY)); if (!AlmostEqualUlps_Pin(largest, largest + minDist)) { // is distance within ULPS tolerance? return -1; } return SkPinT(i[0][minIndex]); } void SkDCurve::setConicBounds(const SkPoint curve[3], SkScalar curveWeight, double tStart, double tEnd, SkPathOpsBounds* bounds) { SkDConic dCurve; dCurve.set(curve, curveWeight); SkDRect dRect; dRect.setBounds(dCurve, fConic, tStart, tEnd); bounds->setLTRB(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); } void SkDCurve::setCubicBounds(const SkPoint curve[4], SkScalar , double tStart, double tEnd, SkPathOpsBounds* bounds) { SkDCubic dCurve; dCurve.set(curve); SkDRect dRect; dRect.setBounds(dCurve, fCubic, tStart, tEnd); bounds->setLTRB(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); } void SkDCurve::setQuadBounds(const SkPoint curve[3], SkScalar , double tStart, double tEnd, SkPathOpsBounds* bounds) { SkDQuad dCurve; dCurve.set(curve); SkDRect dRect; dRect.setBounds(dCurve, fQuad, tStart, tEnd); bounds->setLTRB(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); } void SkDCurveSweep::setCurveHullSweep(SkPath::Verb verb) { fOrdered = true; fSweep[0] = fCurve[1] - fCurve[0]; if (SkPath::kLine_Verb == verb) { fSweep[1] = fSweep[0]; fIsCurve = false; return; } fSweep[1] = fCurve[2] - fCurve[0]; // OPTIMIZE: I do the following float check a lot -- probably need a // central place for this val-is-small-compared-to-curve check double maxVal = 0; for (int index = 0; index <= SkPathOpsVerbToPoints(verb); ++index) { maxVal = std::max(maxVal, std::max(SkTAbs(fCurve[index].fX), SkTAbs(fCurve[index].fY))); } { if (SkPath::kCubic_Verb != verb) { if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { fSweep[0] = fSweep[1]; } goto setIsCurve; } SkDVector thirdSweep = fCurve[3] - fCurve[0]; if (fSweep[0].fX == 0 && fSweep[0].fY == 0) { fSweep[0] = fSweep[1]; fSweep[1] = thirdSweep; if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { fSweep[0] = fSweep[1]; fCurve[1] = fCurve[3]; } goto setIsCurve; } double s1x3 = fSweep[0].crossCheck(thirdSweep); double s3x2 = thirdSweep.crossCheck(fSweep[1]); if (s1x3 * s3x2 >= 0) { // if third vector is on or between first two vectors goto setIsCurve; } double s2x1 = fSweep[1].crossCheck(fSweep[0]); // FIXME: If the sweep of the cubic is greater than 180 degrees, we're in trouble // probably such wide sweeps should be artificially subdivided earlier so that never happens SkASSERT(s1x3 * s2x1 < 0 || s1x3 * s3x2 < 0); if (s3x2 * s2x1 < 0) { SkASSERT(s2x1 * s1x3 > 0); fSweep[0] = fSweep[1]; fOrdered = false; } fSweep[1] = thirdSweep; } setIsCurve: fIsCurve = fSweep[0].crossCheck(fSweep[1]) != 0; } #if DEBUG_DUMP_VERIFY bool SkPathOpsDebug::gDumpOp; // set to true to write op to file before a crash bool SkPathOpsDebug::gVerifyOp; // set to true to compare result against regions #endif bool SkPathOpsDebug::gRunFail; // set to true to check for success on tests known to fail bool SkPathOpsDebug::gVeryVerbose; // set to true to run extensive checking tests #define FAIL_IF_COIN(cond, coin) \ do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, coin); } while (false) #undef FAIL_WITH_NULL_IF #define FAIL_WITH_NULL_IF(cond, span) \ do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, span); } while (false) #define RETURN_FALSE_IF(cond, span) \ do { if (cond) log->record(SkPathOpsDebug::kReturnFalse_Glitch, span); \ } while (false) #if DEBUG_SORT int SkPathOpsDebug::gSortCountDefault = SK_MaxS32; int SkPathOpsDebug::gSortCount; #endif #if DEBUG_ACTIVE_OP const char* SkPathOpsDebug::kPathOpStr[] = {"diff", "sect", "union", "xor", "rdiff"}; #endif #if defined SK_DEBUG || !FORCE_RELEASE int SkPathOpsDebug::gContourID = 0; int SkPathOpsDebug::gSegmentID = 0; bool SkPathOpsDebug::ChaseContains(const SkTDArray& chaseArray, const SkOpSpanBase* span) { for (int index = 0; index < chaseArray.size(); ++index) { const SkOpSpanBase* entry = chaseArray[index]; if (entry == span) { return true; } } return false; } #endif #if DEBUG_ACTIVE_SPANS SkString SkPathOpsDebug::gActiveSpans; #endif #if DEBUG_COIN class SkCoincidentSpans; SkPathOpsDebug::CoinDict SkPathOpsDebug::gCoinSumChangedDict; SkPathOpsDebug::CoinDict SkPathOpsDebug::gCoinSumVisitedDict; static const int kGlitchType_Count = SkPathOpsDebug::kUnalignedTail_Glitch + 1; struct SpanGlitch { const SkOpSpanBase* fBase; const SkOpSpanBase* fSuspect; const SkOpSegment* fSegment; const SkOpSegment* fOppSegment; const SkOpPtT* fCoinSpan; const SkOpPtT* fEndSpan; const SkOpPtT* fOppSpan; const SkOpPtT* fOppEndSpan; double fStartT; double fEndT; double fOppStartT; double fOppEndT; SkPoint fPt; SkPathOpsDebug::GlitchType fType; void dumpType() const; }; struct SkPathOpsDebug::GlitchLog { void init(const SkOpGlobalState* state) { fGlobalState = state; } SpanGlitch* recordCommon(GlitchType type) { SpanGlitch* glitch = fGlitches.append(); glitch->fBase = nullptr; glitch->fSuspect = nullptr; glitch->fSegment = nullptr; glitch->fOppSegment = nullptr; glitch->fCoinSpan = nullptr; glitch->fEndSpan = nullptr; glitch->fOppSpan = nullptr; glitch->fOppEndSpan = nullptr; glitch->fStartT = SK_ScalarNaN; glitch->fEndT = SK_ScalarNaN; glitch->fOppStartT = SK_ScalarNaN; glitch->fOppEndT = SK_ScalarNaN; glitch->fPt = { SK_ScalarNaN, SK_ScalarNaN }; glitch->fType = type; return glitch; } void record(GlitchType type, const SkOpSpanBase* base, const SkOpSpanBase* suspect = nullptr) { SpanGlitch* glitch = recordCommon(type); glitch->fBase = base; glitch->fSuspect = suspect; } void record(GlitchType type, const SkOpSpanBase* base, const SkOpPtT* ptT) { SpanGlitch* glitch = recordCommon(type); glitch->fBase = base; glitch->fCoinSpan = ptT; } void record(GlitchType type, const SkCoincidentSpans* coin, const SkCoincidentSpans* opp = nullptr) { SpanGlitch* glitch = recordCommon(type); glitch->fCoinSpan = coin->coinPtTStart(); glitch->fEndSpan = coin->coinPtTEnd(); if (opp) { glitch->fOppSpan = opp->coinPtTStart(); glitch->fOppEndSpan = opp->coinPtTEnd(); } } void record(GlitchType type, const SkOpSpanBase* base, const SkOpSegment* seg, double t, SkPoint pt) { SpanGlitch* glitch = recordCommon(type); glitch->fBase = base; glitch->fSegment = seg; glitch->fStartT = t; glitch->fPt = pt; } void record(GlitchType type, const SkOpSpanBase* base, double t, SkPoint pt) { SpanGlitch* glitch = recordCommon(type); glitch->fBase = base; glitch->fStartT = t; glitch->fPt = pt; } void record(GlitchType type, const SkCoincidentSpans* coin, const SkOpPtT* coinSpan, const SkOpPtT* endSpan) { SpanGlitch* glitch = recordCommon(type); glitch->fCoinSpan = coin->coinPtTStart(); glitch->fEndSpan = coin->coinPtTEnd(); glitch->fEndSpan = endSpan; glitch->fOppSpan = coinSpan; glitch->fOppEndSpan = endSpan; } void record(GlitchType type, const SkCoincidentSpans* coin, const SkOpSpanBase* base) { SpanGlitch* glitch = recordCommon(type); glitch->fBase = base; glitch->fCoinSpan = coin->coinPtTStart(); glitch->fEndSpan = coin->coinPtTEnd(); } void record(GlitchType type, const SkOpPtT* ptTS, const SkOpPtT* ptTE, const SkOpPtT* oPtTS, const SkOpPtT* oPtTE) { SpanGlitch* glitch = recordCommon(type); glitch->fCoinSpan = ptTS; glitch->fEndSpan = ptTE; glitch->fOppSpan = oPtTS; glitch->fOppEndSpan = oPtTE; } void record(GlitchType type, const SkOpSegment* seg, double startT, double endT, const SkOpSegment* oppSeg, double oppStartT, double oppEndT) { SpanGlitch* glitch = recordCommon(type); glitch->fSegment = seg; glitch->fStartT = startT; glitch->fEndT = endT; glitch->fOppSegment = oppSeg; glitch->fOppStartT = oppStartT; glitch->fOppEndT = oppEndT; } void record(GlitchType type, const SkOpSegment* seg, const SkOpSpan* span) { SpanGlitch* glitch = recordCommon(type); glitch->fSegment = seg; glitch->fBase = span; } void record(GlitchType type, double t, const SkOpSpanBase* span) { SpanGlitch* glitch = recordCommon(type); glitch->fStartT = t; glitch->fBase = span; } void record(GlitchType type, const SkOpSegment* seg) { SpanGlitch* glitch = recordCommon(type); glitch->fSegment = seg; } void record(GlitchType type, const SkCoincidentSpans* coin, const SkOpPtT* ptT) { SpanGlitch* glitch = recordCommon(type); glitch->fCoinSpan = coin->coinPtTStart(); glitch->fEndSpan = ptT; } SkTDArray fGlitches; const SkOpGlobalState* fGlobalState; }; void SkPathOpsDebug::CoinDict::add(const SkPathOpsDebug::CoinDict& dict) { int count = dict.fDict.size(); for (int index = 0; index < count; ++index) { this->add(dict.fDict[index]); } } void SkPathOpsDebug::CoinDict::add(const CoinDictEntry& key) { int count = fDict.size(); for (int index = 0; index < count; ++index) { CoinDictEntry* entry = &fDict[index]; if (entry->fIteration == key.fIteration && entry->fLineNumber == key.fLineNumber) { SkASSERT(!strcmp(entry->fFunctionName, key.fFunctionName)); if (entry->fGlitchType == kUninitialized_Glitch) { entry->fGlitchType = key.fGlitchType; } return; } } *fDict.append() = key; } #endif #if DEBUG_COIN static void missing_coincidence(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { const SkOpContour* contour = contourList; // bool result = false; do { /* result |= */ contour->debugMissingCoincidence(glitches); } while ((contour = contour->next())); return; } static void move_multiples(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { const SkOpContour* contour = contourList; do { contour->debugMoveMultiples(glitches); } while ((contour = contour->next())); return; } static void move_nearby(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { const SkOpContour* contour = contourList; do { contour->debugMoveNearby(glitches); } while ((contour = contour->next())); } #endif #if DEBUG_COIN void SkOpGlobalState::debugAddToCoinChangedDict() { #if DEBUG_COINCIDENCE SkPathOpsDebug::CheckHealth(fContourHead); #endif // see if next coincident operation makes a change; if so, record it SkPathOpsDebug::GlitchLog glitches; const char* funcName = fCoinDictEntry.fFunctionName; if (!strcmp("calc_angles", funcName)) { // } else if (!strcmp("missing_coincidence", funcName)) { missing_coincidence(&glitches, fContourHead); } else if (!strcmp("move_multiples", funcName)) { move_multiples(&glitches, fContourHead); } else if (!strcmp("move_nearby", funcName)) { move_nearby(&glitches, fContourHead); } else if (!strcmp("addExpanded", funcName)) { fCoincidence->debugAddExpanded(&glitches); } else if (!strcmp("addMissing", funcName)) { bool added; fCoincidence->debugAddMissing(&glitches, &added); } else if (!strcmp("addEndMovedSpans", funcName)) { fCoincidence->debugAddEndMovedSpans(&glitches); } else if (!strcmp("correctEnds", funcName)) { fCoincidence->debugCorrectEnds(&glitches); } else if (!strcmp("expand", funcName)) { fCoincidence->debugExpand(&glitches); } else if (!strcmp("findOverlaps", funcName)) { // } else if (!strcmp("mark", funcName)) { fCoincidence->debugMark(&glitches); } else if (!strcmp("apply", funcName)) { // } else { SkASSERT(0); // add missing case } if (glitches.fGlitches.size()) { fCoinDictEntry.fGlitchType = glitches.fGlitches[0].fType; } fCoinChangedDict.add(fCoinDictEntry); } #endif void SkPathOpsDebug::ShowActiveSpans(SkOpContourHead* contourList) { #if DEBUG_ACTIVE_SPANS SkString str; SkOpContour* contour = contourList; do { contour->debugShowActiveSpans(&str); } while ((contour = contour->next())); if (!gActiveSpans.equals(str)) { const char* s = str.c_str(); const char* end; while ((end = strchr(s, '\n'))) { SkDebugf("%.*s", (int) (end - s + 1), s); s = end + 1; } gActiveSpans.set(str); } #endif } #if DEBUG_COINCIDENCE || DEBUG_COIN void SkPathOpsDebug::CheckHealth(SkOpContourHead* contourList) { #if DEBUG_COINCIDENCE contourList->globalState()->debugSetCheckHealth(true); #endif #if DEBUG_COIN GlitchLog glitches; const SkOpContour* contour = contourList; const SkOpCoincidence* coincidence = contour->globalState()->coincidence(); coincidence->debugCheckValid(&glitches); // don't call validate; spans may be inconsistent do { contour->debugCheckHealth(&glitches); contour->debugMissingCoincidence(&glitches); } while ((contour = contour->next())); bool added; coincidence->debugAddMissing(&glitches, &added); coincidence->debugExpand(&glitches); coincidence->debugAddExpanded(&glitches); coincidence->debugMark(&glitches); unsigned mask = 0; for (int index = 0; index < glitches.fGlitches.size(); ++index) { const SpanGlitch& glitch = glitches.fGlitches[index]; mask |= 1 << glitch.fType; } for (int index = 0; index < kGlitchType_Count; ++index) { SkDebugf(mask & (1 << index) ? "x" : "-"); } SkDebugf(" %s\n", contourList->globalState()->debugCoinDictEntry().fFunctionName); for (int index = 0; index < glitches.fGlitches.size(); ++index) { const SpanGlitch& glitch = glitches.fGlitches[index]; SkDebugf("%02d: ", index); if (glitch.fBase) { SkDebugf(" seg/base=%d/%d", glitch.fBase->segment()->debugID(), glitch.fBase->debugID()); } if (glitch.fSuspect) { SkDebugf(" seg/base=%d/%d", glitch.fSuspect->segment()->debugID(), glitch.fSuspect->debugID()); } if (glitch.fSegment) { SkDebugf(" segment=%d", glitch.fSegment->debugID()); } if (glitch.fCoinSpan) { SkDebugf(" coinSeg/Span/PtT=%d/%d/%d", glitch.fCoinSpan->segment()->debugID(), glitch.fCoinSpan->span()->debugID(), glitch.fCoinSpan->debugID()); } if (glitch.fEndSpan) { SkDebugf(" endSpan=%d", glitch.fEndSpan->debugID()); } if (glitch.fOppSpan) { SkDebugf(" oppSeg/Span/PtT=%d/%d/%d", glitch.fOppSpan->segment()->debugID(), glitch.fOppSpan->span()->debugID(), glitch.fOppSpan->debugID()); } if (glitch.fOppEndSpan) { SkDebugf(" oppEndSpan=%d", glitch.fOppEndSpan->debugID()); } if (!SkScalarIsNaN(glitch.fStartT)) { SkDebugf(" startT=%g", glitch.fStartT); } if (!SkScalarIsNaN(glitch.fEndT)) { SkDebugf(" endT=%g", glitch.fEndT); } if (glitch.fOppSegment) { SkDebugf(" segment=%d", glitch.fOppSegment->debugID()); } if (!SkScalarIsNaN(glitch.fOppStartT)) { SkDebugf(" oppStartT=%g", glitch.fOppStartT); } if (!SkScalarIsNaN(glitch.fOppEndT)) { SkDebugf(" oppEndT=%g", glitch.fOppEndT); } if (!SkScalarIsNaN(glitch.fPt.fX) || !SkScalarIsNaN(glitch.fPt.fY)) { SkDebugf(" pt=%g,%g", glitch.fPt.fX, glitch.fPt.fY); } DumpGlitchType(glitch.fType); SkDebugf("\n"); } #if DEBUG_COINCIDENCE contourList->globalState()->debugSetCheckHealth(false); #endif #if 01 && DEBUG_ACTIVE_SPANS // SkDebugf("active after %s:\n", id); ShowActiveSpans(contourList); #endif #endif } #endif #if DEBUG_COIN void SkPathOpsDebug::DumpGlitchType(GlitchType glitchType) { switch (glitchType) { case kAddCorruptCoin_Glitch: SkDebugf(" AddCorruptCoin"); break; case kAddExpandedCoin_Glitch: SkDebugf(" AddExpandedCoin"); break; case kAddExpandedFail_Glitch: SkDebugf(" AddExpandedFail"); break; case kAddIfCollapsed_Glitch: SkDebugf(" AddIfCollapsed"); break; case kAddIfMissingCoin_Glitch: SkDebugf(" AddIfMissingCoin"); break; case kAddMissingCoin_Glitch: SkDebugf(" AddMissingCoin"); break; case kAddMissingExtend_Glitch: SkDebugf(" AddMissingExtend"); break; case kAddOrOverlap_Glitch: SkDebugf(" AAddOrOverlap"); break; case kCollapsedCoin_Glitch: SkDebugf(" CollapsedCoin"); break; case kCollapsedDone_Glitch: SkDebugf(" CollapsedDone"); break; case kCollapsedOppValue_Glitch: SkDebugf(" CollapsedOppValue"); break; case kCollapsedSpan_Glitch: SkDebugf(" CollapsedSpan"); break; case kCollapsedWindValue_Glitch: SkDebugf(" CollapsedWindValue"); break; case kCorrectEnd_Glitch: SkDebugf(" CorrectEnd"); break; case kDeletedCoin_Glitch: SkDebugf(" DeletedCoin"); break; case kExpandCoin_Glitch: SkDebugf(" ExpandCoin"); break; case kFail_Glitch: SkDebugf(" Fail"); break; case kMarkCoinEnd_Glitch: SkDebugf(" MarkCoinEnd"); break; case kMarkCoinInsert_Glitch: SkDebugf(" MarkCoinInsert"); break; case kMarkCoinMissing_Glitch: SkDebugf(" MarkCoinMissing"); break; case kMarkCoinStart_Glitch: SkDebugf(" MarkCoinStart"); break; case kMergeMatches_Glitch: SkDebugf(" MergeMatches"); break; case kMissingCoin_Glitch: SkDebugf(" MissingCoin"); break; case kMissingDone_Glitch: SkDebugf(" MissingDone"); break; case kMissingIntersection_Glitch: SkDebugf(" MissingIntersection"); break; case kMoveMultiple_Glitch: SkDebugf(" MoveMultiple"); break; case kMoveNearbyClearAll_Glitch: SkDebugf(" MoveNearbyClearAll"); break; case kMoveNearbyClearAll2_Glitch: SkDebugf(" MoveNearbyClearAll2"); break; case kMoveNearbyMerge_Glitch: SkDebugf(" MoveNearbyMerge"); break; case kMoveNearbyMergeFinal_Glitch: SkDebugf(" MoveNearbyMergeFinal"); break; case kMoveNearbyRelease_Glitch: SkDebugf(" MoveNearbyRelease"); break; case kMoveNearbyReleaseFinal_Glitch: SkDebugf(" MoveNearbyReleaseFinal"); break; case kReleasedSpan_Glitch: SkDebugf(" ReleasedSpan"); break; case kReturnFalse_Glitch: SkDebugf(" ReturnFalse"); break; case kUnaligned_Glitch: SkDebugf(" Unaligned"); break; case kUnalignedHead_Glitch: SkDebugf(" UnalignedHead"); break; case kUnalignedTail_Glitch: SkDebugf(" UnalignedTail"); break; case kUninitialized_Glitch: break; default: SkASSERT(0); } } #endif #if defined SK_DEBUG || !FORCE_RELEASE void SkPathOpsDebug::MathematicaIze(char* str, size_t bufferLen) { size_t len = strlen(str); bool num = false; for (size_t idx = 0; idx < len; ++idx) { if (num && str[idx] == 'e') { if (len + 2 >= bufferLen) { return; } memmove(&str[idx + 2], &str[idx + 1], len - idx); str[idx] = '*'; str[idx + 1] = '^'; ++len; } num = str[idx] >= '0' && str[idx] <= '9'; } } bool SkPathOpsDebug::ValidWind(int wind) { return wind > SK_MinS32 + 0xFFFF && wind < SK_MaxS32 - 0xFFFF; } void SkPathOpsDebug::WindingPrintf(int wind) { if (wind == SK_MinS32) { SkDebugf("?"); } else { SkDebugf("%d", wind); } } #endif // defined SK_DEBUG || !FORCE_RELEASE static void show_function_header(const char* functionName) { SkDebugf("\nstatic void %s(skiatest::Reporter* reporter, const char* filename) {\n", functionName); if (strcmp("skphealth_com76", functionName) == 0) { SkDebugf("found it\n"); } } static const char* gOpStrs[] = { "kDifference_SkPathOp", "kIntersect_SkPathOp", "kUnion_SkPathOp", "kXOR_PathOp", "kReverseDifference_SkPathOp", }; const char* SkPathOpsDebug::OpStr(SkPathOp op) { return gOpStrs[op]; } static void show_op(SkPathOp op, const char* pathOne, const char* pathTwo) { SkDebugf(" testPathOp(reporter, %s, %s, %s, filename);\n", pathOne, pathTwo, gOpStrs[op]); SkDebugf("}\n"); } void SkPathOpsDebug::ShowPath(const SkPath& a, const SkPath& b, SkPathOp shapeOp, const char* testName) { static SkMutex& mutex = *(new SkMutex); SkAutoMutexExclusive ac(mutex); show_function_header(testName); ShowOnePath(a, "path", true); ShowOnePath(b, "pathB", true); show_op(shapeOp, "path", "pathB"); } #if DEBUG_COIN void SkOpGlobalState::debugAddToGlobalCoinDicts() { static SkMutex& mutex = *(new SkMutex); SkAutoMutexExclusive ac(mutex); SkPathOpsDebug::gCoinSumChangedDict.add(fCoinChangedDict); SkPathOpsDebug::gCoinSumVisitedDict.add(fCoinVisitedDict); } #endif #if DEBUG_T_SECT_LOOP_COUNT void SkOpGlobalState::debugAddLoopCount(SkIntersections* i, const SkIntersectionHelper& wt, const SkIntersectionHelper& wn) { for (int index = 0; index < (int) std::size(fDebugLoopCount); ++index) { SkIntersections::DebugLoop looper = (SkIntersections::DebugLoop) index; if (fDebugLoopCount[index] >= i->debugLoopCount(looper)) { continue; } fDebugLoopCount[index] = i->debugLoopCount(looper); fDebugWorstVerb[index * 2] = wt.segment()->verb(); fDebugWorstVerb[index * 2 + 1] = wn.segment()->verb(); sk_bzero(&fDebugWorstPts[index * 8], sizeof(SkPoint) * 8); memcpy(&fDebugWorstPts[index * 2 * 4], wt.pts(), (SkPathOpsVerbToPoints(wt.segment()->verb()) + 1) * sizeof(SkPoint)); memcpy(&fDebugWorstPts[(index * 2 + 1) * 4], wn.pts(), (SkPathOpsVerbToPoints(wn.segment()->verb()) + 1) * sizeof(SkPoint)); fDebugWorstWeight[index * 2] = wt.weight(); fDebugWorstWeight[index * 2 + 1] = wn.weight(); } i->debugResetLoopCount(); } void SkOpGlobalState::debugDoYourWorst(SkOpGlobalState* local) { for (int index = 0; index < (int) std::size(fDebugLoopCount); ++index) { if (fDebugLoopCount[index] >= local->fDebugLoopCount[index]) { continue; } fDebugLoopCount[index] = local->fDebugLoopCount[index]; fDebugWorstVerb[index * 2] = local->fDebugWorstVerb[index * 2]; fDebugWorstVerb[index * 2 + 1] = local->fDebugWorstVerb[index * 2 + 1]; memcpy(&fDebugWorstPts[index * 2 * 4], &local->fDebugWorstPts[index * 2 * 4], sizeof(SkPoint) * 8); fDebugWorstWeight[index * 2] = local->fDebugWorstWeight[index * 2]; fDebugWorstWeight[index * 2 + 1] = local->fDebugWorstWeight[index * 2 + 1]; } local->debugResetLoopCounts(); } static void dump_curve(SkPath::Verb verb, const SkPoint& pts, float weight) { if (!verb) { return; } const char* verbs[] = { "", "line", "quad", "conic", "cubic" }; SkDebugf("%s: {{", verbs[verb]); int ptCount = SkPathOpsVerbToPoints(verb); for (int index = 0; index <= ptCount; ++index) { SkDPoint::Dump((&pts)[index]); if (index < ptCount - 1) { SkDebugf(", "); } } SkDebugf("}"); if (weight != 1) { SkDebugf(", "); if (weight == floorf(weight)) { SkDebugf("%.0f", weight); } else { SkDebugf("%1.9gf", weight); } } SkDebugf("}\n"); } void SkOpGlobalState::debugLoopReport() { const char* loops[] = { "iterations", "coinChecks", "perpCalcs" }; SkDebugf("\n"); for (int index = 0; index < (int) std::size(fDebugLoopCount); ++index) { SkDebugf("%s: %d\n", loops[index], fDebugLoopCount[index]); dump_curve(fDebugWorstVerb[index * 2], fDebugWorstPts[index * 2 * 4], fDebugWorstWeight[index * 2]); dump_curve(fDebugWorstVerb[index * 2 + 1], fDebugWorstPts[(index * 2 + 1) * 4], fDebugWorstWeight[index * 2 + 1]); } } void SkOpGlobalState::debugResetLoopCounts() { sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); sk_bzero(fDebugWorstVerb, sizeof(fDebugWorstVerb)); sk_bzero(fDebugWorstPts, sizeof(fDebugWorstPts)); sk_bzero(fDebugWorstWeight, sizeof(fDebugWorstWeight)); } #endif bool SkOpGlobalState::DebugRunFail() { return SkPathOpsDebug::gRunFail; } // this is const so it can be called by const methods that overwise don't alter state #if DEBUG_VALIDATE || DEBUG_COIN void SkOpGlobalState::debugSetPhase(const char* funcName DEBUG_COIN_DECLARE_PARAMS()) const { auto writable = const_cast(this); #if DEBUG_VALIDATE writable->setPhase(phase); #endif #if DEBUG_COIN SkPathOpsDebug::CoinDictEntry* entry = &writable->fCoinDictEntry; writable->fPreviousFuncName = entry->fFunctionName; entry->fIteration = iteration; entry->fLineNumber = lineNo; entry->fGlitchType = SkPathOpsDebug::kUninitialized_Glitch; entry->fFunctionName = funcName; writable->fCoinVisitedDict.add(*entry); writable->debugAddToCoinChangedDict(); #endif } #endif #if DEBUG_T_SECT_LOOP_COUNT void SkIntersections::debugBumpLoopCount(DebugLoop index) { fDebugLoopCount[index]++; } int SkIntersections::debugLoopCount(DebugLoop index) const { return fDebugLoopCount[index]; } void SkIntersections::debugResetLoopCount() { sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); } #endif SkDCubic SkDQuad::debugToCubic() const { SkDCubic cubic; cubic[0] = fPts[0]; cubic[2] = fPts[1]; cubic[3] = fPts[2]; cubic[1].fX = (cubic[0].fX + cubic[2].fX * 2) / 3; cubic[1].fY = (cubic[0].fY + cubic[2].fY * 2) / 3; cubic[2].fX = (cubic[3].fX + cubic[2].fX * 2) / 3; cubic[2].fY = (cubic[3].fY + cubic[2].fY * 2) / 3; return cubic; } void SkDQuad::debugSet(const SkDPoint* pts) { memcpy(fPts, pts, sizeof(fPts)); SkDEBUGCODE(fDebugGlobalState = nullptr); } void SkDCubic::debugSet(const SkDPoint* pts) { memcpy(fPts, pts, sizeof(fPts)); SkDEBUGCODE(fDebugGlobalState = nullptr); } void SkDConic::debugSet(const SkDPoint* pts, SkScalar weight) { fPts.debugSet(pts); fWeight = weight; } void SkDRect::debugInit() { fLeft = fTop = fRight = fBottom = SK_ScalarNaN; } #if DEBUG_COIN // commented-out lines keep this in sync with addT() const SkOpPtT* SkOpSegment::debugAddT(double t, SkPathOpsDebug::GlitchLog* log) const { debugValidate(); SkPoint pt = this->ptAtT(t); const SkOpSpanBase* span = &fHead; do { const SkOpPtT* result = span->ptT(); if (t == result->fT || this->match(result, this, t, pt)) { // span->bumpSpanAdds(); return result; } if (t < result->fT) { const SkOpSpan* prev = result->span()->prev(); FAIL_WITH_NULL_IF(!prev, span); // marks in global state that new op span has been allocated this->globalState()->setAllocatedOpSpan(); // span->init(this, prev, t, pt); this->debugValidate(); // #if DEBUG_ADD_T // SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, // span->segment()->debugID(), span->debugID()); // #endif // span->bumpSpanAdds(); return nullptr; } FAIL_WITH_NULL_IF(span != &fTail, span); } while ((span = span->upCast()->next())); SkASSERT(0); return nullptr; // we never get here, but need this to satisfy compiler } #endif #if DEBUG_ANGLE void SkOpSegment::debugCheckAngleCoin() const { const SkOpSpanBase* base = &fHead; const SkOpSpan* span; do { const SkOpAngle* angle = base->fromAngle(); if (angle && angle->debugCheckCoincidence()) { angle->debugCheckNearCoincidence(); } if (base->final()) { break; } span = base->upCast(); angle = span->toAngle(); if (angle && angle->debugCheckCoincidence()) { angle->debugCheckNearCoincidence(); } } while ((base = span->next())); } #endif #if DEBUG_COIN // this mimics the order of the checks in handle coincidence void SkOpSegment::debugCheckHealth(SkPathOpsDebug::GlitchLog* glitches) const { debugMoveMultiples(glitches); debugMoveNearby(glitches); debugMissingCoincidence(glitches); } // commented-out lines keep this in sync with clearAll() void SkOpSegment::debugClearAll(SkPathOpsDebug::GlitchLog* glitches) const { const SkOpSpan* span = &fHead; do { this->debugClearOne(span, glitches); } while ((span = span->next()->upCastable())); this->globalState()->coincidence()->debugRelease(glitches, this); } // commented-out lines keep this in sync with clearOne() void SkOpSegment::debugClearOne(const SkOpSpan* span, SkPathOpsDebug::GlitchLog* glitches) const { if (span->windValue()) glitches->record(SkPathOpsDebug::kCollapsedWindValue_Glitch, span); if (span->oppValue()) glitches->record(SkPathOpsDebug::kCollapsedOppValue_Glitch, span); if (!span->done()) glitches->record(SkPathOpsDebug::kCollapsedDone_Glitch, span); } #endif SkOpAngle* SkOpSegment::debugLastAngle() { SkOpAngle* result = nullptr; SkOpSpan* span = this->head(); do { if (span->toAngle()) { SkASSERT(!result); result = span->toAngle(); } } while ((span = span->next()->upCastable())); SkASSERT(result); return result; } #if DEBUG_COIN // commented-out lines keep this in sync with ClearVisited void SkOpSegment::DebugClearVisited(const SkOpSpanBase* span) { // reset visited flag back to false do { const SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; while ((ptT = ptT->next()) != stopPtT) { const SkOpSegment* opp = ptT->segment(); opp->resetDebugVisited(); } } while (!span->final() && (span = span->upCast()->next())); } #endif #if DEBUG_COIN // commented-out lines keep this in sync with missingCoincidence() // look for pairs of undetected coincident curves // assumes that segments going in have visited flag clear // Even though pairs of curves correct detect coincident runs, a run may be missed // if the coincidence is a product of multiple intersections. For instance, given // curves A, B, and C: // A-B intersect at a point 1; A-C and B-C intersect at point 2, so near // the end of C that the intersection is replaced with the end of C. // Even though A-B correctly do not detect an intersection at point 2, // the resulting run from point 1 to point 2 is coincident on A and B. void SkOpSegment::debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const { if (this->done()) { return; } const SkOpSpan* prior = nullptr; const SkOpSpanBase* spanBase = &fHead; // bool result = false; do { const SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; SkASSERT(ptT->span() == spanBase); while ((ptT = ptT->next()) != spanStopPtT) { if (ptT->deleted()) { continue; } const SkOpSegment* opp = ptT->span()->segment(); if (opp->done()) { continue; } // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence if (!opp->debugVisited()) { continue; } if (spanBase == &fHead) { continue; } if (ptT->segment() == this) { continue; } const SkOpSpan* span = spanBase->upCastable(); // FIXME?: this assumes that if the opposite segment is coincident then no more // coincidence needs to be detected. This may not be true. if (span && span->segment() != opp && span->containsCoincidence(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted continue; } if (spanBase->segment() != opp && spanBase->containsCoinEnd(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted continue; } const SkOpPtT* priorPtT = nullptr, * priorStopPtT; // find prior span containing opp segment const SkOpSegment* priorOpp = nullptr; const SkOpSpan* priorTest = spanBase->prev(); while (!priorOpp && priorTest) { priorStopPtT = priorPtT = priorTest->ptT(); while ((priorPtT = priorPtT->next()) != priorStopPtT) { if (priorPtT->deleted()) { continue; } const SkOpSegment* segment = priorPtT->span()->segment(); if (segment == opp) { prior = priorTest; priorOpp = opp; break; } } priorTest = priorTest->prev(); } if (!priorOpp) { continue; } if (priorPtT == ptT) { continue; } const SkOpPtT* oppStart = prior->ptT(); const SkOpPtT* oppEnd = spanBase->ptT(); bool swapped = priorPtT->fT > ptT->fT; if (swapped) { using std::swap; swap(priorPtT, ptT); swap(oppStart, oppEnd); } const SkOpCoincidence* coincidence = this->globalState()->coincidence(); const SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); const SkOpPtT* rootPtT = ptT->span()->ptT(); const SkOpPtT* rootOppStart = oppStart->span()->ptT(); const SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); if (coincidence->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { goto swapBack; } if (testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { // mark coincidence #if DEBUG_COINCIDENCE_VERBOSE // SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, // rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), // rootOppEnd->debugID()); #endif log->record(SkPathOpsDebug::kMissingCoin_Glitch, priorPtT, ptT, oppStart, oppEnd); // coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); // } #if DEBUG_COINCIDENCE // SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); #endif // result = true; } swapBack: if (swapped) { using std::swap; swap(priorPtT, ptT); } } } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); DebugClearVisited(&fHead); return; } // commented-out lines keep this in sync with moveMultiples() // if a span has more than one intersection, merge the other segments' span as needed void SkOpSegment::debugMoveMultiples(SkPathOpsDebug::GlitchLog* glitches) const { debugValidate(); const SkOpSpanBase* test = &fHead; do { int addCount = test->spanAddsCount(); // SkASSERT(addCount >= 1); if (addCount <= 1) { continue; } const SkOpPtT* startPtT = test->ptT(); const SkOpPtT* testPtT = startPtT; do { // iterate through all spans associated with start const SkOpSpanBase* oppSpan = testPtT->span(); if (oppSpan->spanAddsCount() == addCount) { continue; } if (oppSpan->deleted()) { continue; } const SkOpSegment* oppSegment = oppSpan->segment(); if (oppSegment == this) { continue; } // find range of spans to consider merging const SkOpSpanBase* oppPrev = oppSpan; const SkOpSpanBase* oppFirst = oppSpan; while ((oppPrev = oppPrev->prev())) { if (!roughly_equal(oppPrev->t(), oppSpan->t())) { break; } if (oppPrev->spanAddsCount() == addCount) { continue; } if (oppPrev->deleted()) { continue; } oppFirst = oppPrev; } const SkOpSpanBase* oppNext = oppSpan; const SkOpSpanBase* oppLast = oppSpan; while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { if (!roughly_equal(oppNext->t(), oppSpan->t())) { break; } if (oppNext->spanAddsCount() == addCount) { continue; } if (oppNext->deleted()) { continue; } oppLast = oppNext; } if (oppFirst == oppLast) { continue; } const SkOpSpanBase* oppTest = oppFirst; do { if (oppTest == oppSpan) { continue; } // check to see if the candidate meets specific criteria: // it contains spans of segments in test's loop but not including 'this' const SkOpPtT* oppStartPtT = oppTest->ptT(); const SkOpPtT* oppPtT = oppStartPtT; while ((oppPtT = oppPtT->next()) != oppStartPtT) { const SkOpSegment* oppPtTSegment = oppPtT->segment(); if (oppPtTSegment == this) { goto tryNextSpan; } const SkOpPtT* matchPtT = startPtT; do { if (matchPtT->segment() == oppPtTSegment) { goto foundMatch; } } while ((matchPtT = matchPtT->next()) != startPtT); goto tryNextSpan; foundMatch: // merge oppTest and oppSpan oppSegment->debugValidate(); oppTest->debugMergeMatches(glitches, oppSpan); oppTest->debugAddOpp(glitches, oppSpan); oppSegment->debugValidate(); goto checkNextSpan; } tryNextSpan: ; } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); } while ((testPtT = testPtT->next()) != startPtT); checkNextSpan: ; } while ((test = test->final() ? nullptr : test->upCast()->next())); debugValidate(); return; } // commented-out lines keep this in sync with moveNearby() // Move nearby t values and pts so they all hang off the same span. Alignment happens later. void SkOpSegment::debugMoveNearby(SkPathOpsDebug::GlitchLog* glitches) const { debugValidate(); // release undeleted spans pointing to this seg that are linked to the primary span const SkOpSpanBase* spanBase = &fHead; do { const SkOpPtT* ptT = spanBase->ptT(); const SkOpPtT* headPtT = ptT; while ((ptT = ptT->next()) != headPtT) { const SkOpSpanBase* test = ptT->span(); if (ptT->segment() == this && !ptT->deleted() && test != spanBase && test->ptT() == ptT) { if (test->final()) { if (spanBase == &fHead) { glitches->record(SkPathOpsDebug::kMoveNearbyClearAll_Glitch, this); // return; } glitches->record(SkPathOpsDebug::kMoveNearbyReleaseFinal_Glitch, spanBase, ptT); } else if (test->prev()) { glitches->record(SkPathOpsDebug::kMoveNearbyRelease_Glitch, test, headPtT); } // break; } } spanBase = spanBase->upCast()->next(); } while (!spanBase->final()); // This loop looks for adjacent spans which are near by spanBase = &fHead; do { // iterate through all spans associated with start const SkOpSpanBase* test = spanBase->upCast()->next(); bool found; if (!this->spansNearby(spanBase, test, &found)) { glitches->record(SkPathOpsDebug::kMoveNearbyMergeFinal_Glitch, test); } if (found) { if (test->final()) { if (spanBase->prev()) { glitches->record(SkPathOpsDebug::kMoveNearbyMergeFinal_Glitch, test); } else { glitches->record(SkPathOpsDebug::kMoveNearbyClearAll2_Glitch, this); // return } } else { glitches->record(SkPathOpsDebug::kMoveNearbyMerge_Glitch, spanBase); } } spanBase = test; } while (!spanBase->final()); debugValidate(); } #endif void SkOpSegment::debugReset() { this->init(this->fPts, this->fWeight, this->contour(), this->verb()); } #if DEBUG_COINCIDENCE_ORDER void SkOpSegment::debugSetCoinT(int index, SkScalar t) const { if (fDebugBaseMax < 0 || fDebugBaseIndex == index) { fDebugBaseIndex = index; fDebugBaseMin = std::min(t, fDebugBaseMin); fDebugBaseMax = std::max(t, fDebugBaseMax); return; } SkASSERT(fDebugBaseMin >= t || t >= fDebugBaseMax); if (fDebugLastMax < 0 || fDebugLastIndex == index) { fDebugLastIndex = index; fDebugLastMin = std::min(t, fDebugLastMin); fDebugLastMax = std::max(t, fDebugLastMax); return; } SkASSERT(fDebugLastMin >= t || t >= fDebugLastMax); SkASSERT((t - fDebugBaseMin > 0) == (fDebugLastMin - fDebugBaseMin > 0)); } #endif #if DEBUG_ACTIVE_SPANS void SkOpSegment::debugShowActiveSpans(SkString* str) const { debugValidate(); if (done()) { return; } int lastId = -1; double lastT = -1; const SkOpSpan* span = &fHead; do { if (span->done()) { continue; } if (lastId == this->debugID() && lastT == span->t()) { continue; } lastId = this->debugID(); lastT = span->t(); str->appendf("%s id=%d", __FUNCTION__, this->debugID()); // since endpoints may have be adjusted, show actual computed curves SkDCurve curvePart; this->subDivide(span, span->next(), &curvePart); const SkDPoint* pts = curvePart.fCubic.fPts; str->appendf(" (%1.9g,%1.9g", pts[0].fX, pts[0].fY); for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { str->appendf(" %1.9g,%1.9g", pts[vIndex].fX, pts[vIndex].fY); } if (SkPath::kConic_Verb == fVerb) { str->appendf(" %1.9gf", curvePart.fConic.fWeight); } str->appendf(") t=%1.9g tEnd=%1.9g", span->t(), span->next()->t()); if (span->windSum() == SK_MinS32) { str->appendf(" windSum=?"); } else { str->appendf(" windSum=%d", span->windSum()); } if (span->oppValue() && span->oppSum() == SK_MinS32) { str->appendf(" oppSum=?"); } else if (span->oppValue() || span->oppSum() != SK_MinS32) { str->appendf(" oppSum=%d", span->oppSum()); } str->appendf(" windValue=%d", span->windValue()); if (span->oppValue() || span->oppSum() != SK_MinS32) { str->appendf(" oppValue=%d", span->oppValue()); } str->appendf("\n"); } while ((span = span->next()->upCastable())); } #endif #if DEBUG_MARK_DONE void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding) { const SkPoint& pt = span->ptT()->fPt; SkDebugf("%s id=%d", fun, this->debugID()); SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); } SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t()); if (winding == SK_MinS32) { SkDebugf("?"); } else { SkDebugf("%d", winding); } SkDebugf(" windSum="); if (span->windSum() == SK_MinS32) { SkDebugf("?"); } else { SkDebugf("%d", span->windSum()); } SkDebugf(" windValue=%d\n", span->windValue()); } void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, int oppWinding) { const SkPoint& pt = span->ptT()->fPt; SkDebugf("%s id=%d", fun, this->debugID()); SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); } SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t()); if (winding == SK_MinS32) { SkDebugf("?"); } else { SkDebugf("%d", winding); } SkDebugf(" newOppSum="); if (oppWinding == SK_MinS32) { SkDebugf("?"); } else { SkDebugf("%d", oppWinding); } SkDebugf(" oppSum="); if (span->oppSum() == SK_MinS32) { SkDebugf("?"); } else { SkDebugf("%d", span->oppSum()); } SkDebugf(" windSum="); if (span->windSum() == SK_MinS32) { SkDebugf("?"); } else { SkDebugf("%d", span->windSum()); } SkDebugf(" windValue=%d oppValue=%d\n", span->windValue(), span->oppValue()); } #endif // loop looking for a pair of angle parts that are too close to be sorted /* This is called after other more simple intersection and angle sorting tests have been exhausted. This should be rarely called -- the test below is thorough and time consuming. This checks the distance between start points; the distance between */ #if DEBUG_ANGLE void SkOpAngle::debugCheckNearCoincidence() const { const SkOpAngle* test = this; do { const SkOpSegment* testSegment = test->segment(); double testStartT = test->start()->t(); SkDPoint testStartPt = testSegment->dPtAtT(testStartT); double testEndT = test->end()->t(); SkDPoint testEndPt = testSegment->dPtAtT(testEndT); double testLenSq = testStartPt.distanceSquared(testEndPt); SkDebugf("%s testLenSq=%1.9g id=%d\n", __FUNCTION__, testLenSq, testSegment->debugID()); double testMidT = (testStartT + testEndT) / 2; const SkOpAngle* next = test; while ((next = next->fNext) != this) { SkOpSegment* nextSegment = next->segment(); double testMidDistSq = testSegment->distSq(testMidT, next); double testEndDistSq = testSegment->distSq(testEndT, next); double nextStartT = next->start()->t(); SkDPoint nextStartPt = nextSegment->dPtAtT(nextStartT); double distSq = testStartPt.distanceSquared(nextStartPt); double nextEndT = next->end()->t(); double nextMidT = (nextStartT + nextEndT) / 2; double nextMidDistSq = nextSegment->distSq(nextMidT, test); double nextEndDistSq = nextSegment->distSq(nextEndT, test); SkDebugf("%s distSq=%1.9g testId=%d nextId=%d\n", __FUNCTION__, distSq, testSegment->debugID(), nextSegment->debugID()); SkDebugf("%s testMidDistSq=%1.9g\n", __FUNCTION__, testMidDistSq); SkDebugf("%s testEndDistSq=%1.9g\n", __FUNCTION__, testEndDistSq); SkDebugf("%s nextMidDistSq=%1.9g\n", __FUNCTION__, nextMidDistSq); SkDebugf("%s nextEndDistSq=%1.9g\n", __FUNCTION__, nextEndDistSq); SkDPoint nextEndPt = nextSegment->dPtAtT(nextEndT); double nextLenSq = nextStartPt.distanceSquared(nextEndPt); SkDebugf("%s nextLenSq=%1.9g\n", __FUNCTION__, nextLenSq); SkDebugf("\n"); } test = test->fNext; } while (test->fNext != this); } #endif #if DEBUG_ANGLE SkString SkOpAngle::debugPart() const { SkString result; switch (this->segment()->verb()) { case SkPath::kLine_Verb: result.printf(LINE_DEBUG_STR " id=%d", LINE_DEBUG_DATA(fPart.fCurve), this->segment()->debugID()); break; case SkPath::kQuad_Verb: result.printf(QUAD_DEBUG_STR " id=%d", QUAD_DEBUG_DATA(fPart.fCurve), this->segment()->debugID()); break; case SkPath::kConic_Verb: result.printf(CONIC_DEBUG_STR " id=%d", CONIC_DEBUG_DATA(fPart.fCurve, fPart.fCurve.fConic.fWeight), this->segment()->debugID()); break; case SkPath::kCubic_Verb: result.printf(CUBIC_DEBUG_STR " id=%d", CUBIC_DEBUG_DATA(fPart.fCurve), this->segment()->debugID()); break; default: SkASSERT(0); } return result; } #endif #if DEBUG_SORT void SkOpAngle::debugLoop() const { const SkOpAngle* first = this; const SkOpAngle* next = this; do { next->dumpOne(true); SkDebugf("\n"); next = next->fNext; } while (next && next != first); next = first; do { next->debugValidate(); next = next->fNext; } while (next && next != first); } #endif void SkOpAngle::debugValidate() const { #if DEBUG_COINCIDENCE if (this->globalState()->debugCheckHealth()) { return; } #endif #if DEBUG_VALIDATE const SkOpAngle* first = this; const SkOpAngle* next = this; int wind = 0; int opp = 0; int lastXor = -1; int lastOppXor = -1; do { if (next->unorderable()) { return; } const SkOpSpan* minSpan = next->start()->starter(next->end()); if (minSpan->windValue() == SK_MinS32) { return; } bool op = next->segment()->operand(); bool isXor = next->segment()->isXor(); bool oppXor = next->segment()->oppXor(); SkASSERT(!DEBUG_LIMIT_WIND_SUM || between(0, minSpan->windValue(), DEBUG_LIMIT_WIND_SUM)); SkASSERT(!DEBUG_LIMIT_WIND_SUM || between(-DEBUG_LIMIT_WIND_SUM, minSpan->oppValue(), DEBUG_LIMIT_WIND_SUM)); bool useXor = op ? oppXor : isXor; SkASSERT(lastXor == -1 || lastXor == (int) useXor); lastXor = (int) useXor; wind += next->debugSign() * (op ? minSpan->oppValue() : minSpan->windValue()); if (useXor) { wind &= 1; } useXor = op ? isXor : oppXor; SkASSERT(lastOppXor == -1 || lastOppXor == (int) useXor); lastOppXor = (int) useXor; opp += next->debugSign() * (op ? minSpan->windValue() : minSpan->oppValue()); if (useXor) { opp &= 1; } next = next->fNext; } while (next && next != first); SkASSERT(wind == 0 || !SkPathOpsDebug::gRunFail); SkASSERT(opp == 0 || !SkPathOpsDebug::gRunFail); #endif } void SkOpAngle::debugValidateNext() const { #if !FORCE_RELEASE const SkOpAngle* first = this; const SkOpAngle* next = first; SkTDArray angles; do { // SkASSERT_RELEASE(next->fSegment->debugContains(next)); angles.push_back(next); next = next->next(); if (next == first) { break; } SkASSERT_RELEASE(!angles.contains(next)); if (!next) { return; } } while (true); #endif } #ifdef SK_DEBUG void SkCoincidentSpans::debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, const SkOpGlobalState* debugState) const { SkASSERT(coinPtTEnd()->span() == over || !SkOpGlobalState::DebugRunFail()); SkASSERT(oppPtTEnd()->span() == outer || !SkOpGlobalState::DebugRunFail()); } #endif #if DEBUG_COIN // sets the span's end to the ptT referenced by the previous-next void SkCoincidentSpans::debugCorrectOneEnd(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) const ) const { const SkOpPtT* origPtT = (this->*getEnd)(); const SkOpSpanBase* origSpan = origPtT->span(); const SkOpSpan* prev = origSpan->prev(); const SkOpPtT* testPtT = prev ? prev->next()->ptT() : origSpan->upCast()->next()->prev()->ptT(); if (origPtT != testPtT) { log->record(SkPathOpsDebug::kCorrectEnd_Glitch, this, origPtT, testPtT); } } /* Commented-out lines keep this in sync with correctEnds */ // FIXME: member pointers have fallen out of favor and can be replaced with // an alternative approach. // makes all span ends agree with the segment's spans that define them void SkCoincidentSpans::debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const { this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTStart, nullptr); this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTEnd, nullptr); this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTStart, nullptr); this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTEnd, nullptr); } /* Commented-out lines keep this in sync with expand */ // expand the range by checking adjacent spans for coincidence bool SkCoincidentSpans::debugExpand(SkPathOpsDebug::GlitchLog* log) const { bool expanded = false; const SkOpSegment* segment = coinPtTStart()->segment(); const SkOpSegment* oppSegment = oppPtTStart()->segment(); do { const SkOpSpan* start = coinPtTStart()->span()->upCast(); const SkOpSpan* prev = start->prev(); const SkOpPtT* oppPtT; if (!prev || !(oppPtT = prev->contains(oppSegment))) { break; } double midT = (prev->t() + start->t()) / 2; if (!segment->isClose(midT, oppSegment)) { break; } if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, prev->ptT(), oppPtT); expanded = true; } while (false); // actual continues while expansion is possible do { const SkOpSpanBase* end = coinPtTEnd()->span(); SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); if (next && next->deleted()) { break; } const SkOpPtT* oppPtT; if (!next || !(oppPtT = next->contains(oppSegment))) { break; } double midT = (end->t() + next->t()) / 2; if (!segment->isClose(midT, oppSegment)) { break; } if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, next->ptT(), oppPtT); expanded = true; } while (false); // actual continues while expansion is possible return expanded; } // description below void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log, const SkOpSpan* base, const SkOpSpanBase* testSpan) const { const SkOpPtT* testPtT = testSpan->ptT(); const SkOpPtT* stopPtT = testPtT; const SkOpSegment* baseSeg = base->segment(); while ((testPtT = testPtT->next()) != stopPtT) { const SkOpSegment* testSeg = testPtT->segment(); if (testPtT->deleted()) { continue; } if (testSeg == baseSeg) { continue; } if (testPtT->span()->ptT() != testPtT) { continue; } if (this->contains(baseSeg, testSeg, testPtT->fT)) { continue; } // intersect perp with base->ptT() with testPtT->segment() SkDVector dxdy = baseSeg->dSlopeAtT(base->t()); const SkPoint& pt = base->pt(); SkDLine ray = {{{pt.fX, pt.fY}, {pt.fX + dxdy.fY, pt.fY - dxdy.fX}}}; SkIntersections i; (*CurveIntersectRay[testSeg->verb()])(testSeg->pts(), testSeg->weight(), ray, &i); for (int index = 0; index < i.used(); ++index) { double t = i[0][index]; if (!between(0, t, 1)) { continue; } SkDPoint oppPt = i.pt(index); if (!oppPt.approximatelyEqual(pt)) { continue; } SkOpSegment* writableSeg = const_cast(testSeg); SkOpPtT* oppStart = writableSeg->addT(t); if (oppStart == testPtT) { continue; } SkOpSpan* writableBase = const_cast(base); oppStart->span()->addOpp(writableBase); if (oppStart->deleted()) { continue; } SkOpSegment* coinSeg = base->segment(); SkOpSegment* oppSeg = oppStart->segment(); double coinTs, coinTe, oppTs, oppTe; if (Ordered(coinSeg, oppSeg)) { coinTs = base->t(); coinTe = testSpan->t(); oppTs = oppStart->fT; oppTe = testPtT->fT; } else { using std::swap; swap(coinSeg, oppSeg); coinTs = oppStart->fT; coinTe = testPtT->fT; oppTs = base->t(); oppTe = testSpan->t(); } if (coinTs > coinTe) { using std::swap; swap(coinTs, coinTe); swap(oppTs, oppTe); } bool added; this->debugAddOrOverlap(log, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &added); } } return; } // description below void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* ptT) const { FAIL_IF_COIN(!ptT->span()->upCastable(), ptT->span()); const SkOpSpan* base = ptT->span()->upCast(); const SkOpSpan* prev = base->prev(); FAIL_IF_COIN(!prev, ptT->span()); if (!prev->isCanceled()) { this->debugAddEndMovedSpans(log, base, base->prev()); } if (!base->isCanceled()) { this->debugAddEndMovedSpans(log, base, base->next()); } return; } /* If A is coincident with B and B includes an endpoint, and A's matching point is not the endpoint (i.e., there's an implied line connecting B-end and A) then assume that the same implied line may intersect another curve close to B. Since we only care about coincidence that was undetected, look at the ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but next door) and see if the A matching point is close enough to form another coincident pair. If so, check for a new coincident span between B-end/A ptT loop and the adjacent ptT loop. */ void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* span = fHead; if (!span) { return; } // fTop = span; // fHead = nullptr; do { if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) { FAIL_IF_COIN(1 == span->coinPtTStart()->fT, span); bool onEnd = span->coinPtTStart()->fT == 0; bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); if (onEnd) { if (!oOnEnd) { // if both are on end, any nearby intersect was already found this->debugAddEndMovedSpans(log, span->oppPtTStart()); } } else if (oOnEnd) { this->debugAddEndMovedSpans(log, span->coinPtTStart()); } } if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { bool onEnd = span->coinPtTEnd()->fT == 1; bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); if (onEnd) { if (!oOnEnd) { this->debugAddEndMovedSpans(log, span->oppPtTEnd()); } } else if (oOnEnd) { this->debugAddEndMovedSpans(log, span->coinPtTEnd()); } } } while ((span = span->next())); // this->restoreHead(); return; } /* Commented-out lines keep this in sync with addExpanded */ // for each coincident pair, match the spans // if the spans don't match, add the mssing pt to the segment and loop it in the opposite span void SkOpCoincidence::debugAddExpanded(SkPathOpsDebug::GlitchLog* log) const { // DEBUG_SET_PHASE(); const SkCoincidentSpans* coin = this->fHead; if (!coin) { return; } do { const SkOpPtT* startPtT = coin->coinPtTStart(); const SkOpPtT* oStartPtT = coin->oppPtTStart(); double priorT = startPtT->fT; double oPriorT = oStartPtT->fT; FAIL_IF_COIN(!startPtT->contains(oStartPtT), coin); SkOPASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); const SkOpSpanBase* start = startPtT->span(); const SkOpSpanBase* oStart = oStartPtT->span(); const SkOpSpanBase* end = coin->coinPtTEnd()->span(); const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); FAIL_IF_COIN(oEnd->deleted(), coin); FAIL_IF_COIN(!start->upCastable(), coin); const SkOpSpanBase* test = start->upCast()->next(); FAIL_IF_COIN(!coin->flipped() && !oStart->upCastable(), coin); const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); FAIL_IF_COIN(!oTest, coin); const SkOpSegment* seg = start->segment(); const SkOpSegment* oSeg = oStart->segment(); while (test != end || oTest != oEnd) { const SkOpPtT* containedOpp = test->ptT()->contains(oSeg); const SkOpPtT* containedThis = oTest->ptT()->contains(seg); if (!containedOpp || !containedThis) { // choose the ends, or the first common pt-t list shared by both double nextT, oNextT; if (containedOpp) { nextT = test->t(); oNextT = containedOpp->fT; } else if (containedThis) { nextT = containedThis->fT; oNextT = oTest->t(); } else { // iterate through until a pt-t list found that contains the other const SkOpSpanBase* walk = test; const SkOpPtT* walkOpp; do { FAIL_IF_COIN(!walk->upCastable(), coin); walk = walk->upCast()->next(); } while (!(walkOpp = walk->ptT()->contains(oSeg)) && walk != coin->coinPtTEnd()->span()); FAIL_IF_COIN(!walkOpp, coin); nextT = walk->t(); oNextT = walkOpp->fT; } // use t ranges to guess which one is missing double startRange = nextT - priorT; FAIL_IF_COIN(!startRange, coin); double startPart = (test->t() - priorT) / startRange; double oStartRange = oNextT - oPriorT; FAIL_IF_COIN(!oStartRange, coin); double oStartPart = (oTest->t() - oStartPtT->fT) / oStartRange; FAIL_IF_COIN(startPart == oStartPart, coin); bool addToOpp = !containedOpp && !containedThis ? startPart < oStartPart : !!containedThis; bool startOver = false; addToOpp ? log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch, oPriorT + oStartRange * startPart, test) : log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch, priorT + startRange * oStartPart, oTest); // FAIL_IF_COIN(!success, coin); if (startOver) { test = start; oTest = oStart; } end = coin->coinPtTEnd()->span(); oEnd = coin->oppPtTEnd()->span(); } if (test != end) { FAIL_IF_COIN(!test->upCastable(), coin); priorT = test->t(); test = test->upCast()->next(); } if (oTest != oEnd) { oPriorT = oTest->t(); oTest = coin->flipped() ? oTest->prev() : oTest->upCast()->next(); FAIL_IF_COIN(!oTest, coin); } } } while ((coin = coin->next())); return; } /* Commented-out lines keep this in sync addIfMissing() */ // note that over1s, over1e, over2s, over2e are ordered void SkOpCoincidence::debugAddIfMissing(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* over1s, const SkOpPtT* over2s, double tStart, double tEnd, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, bool* added, const SkOpPtT* over1e, const SkOpPtT* over2e) const { SkASSERT(tStart < tEnd); SkASSERT(over1s->fT < over1e->fT); SkASSERT(between(over1s->fT, tStart, over1e->fT)); SkASSERT(between(over1s->fT, tEnd, over1e->fT)); SkASSERT(over2s->fT < over2e->fT); SkASSERT(between(over2s->fT, tStart, over2e->fT)); SkASSERT(between(over2s->fT, tEnd, over2e->fT)); SkASSERT(over1s->segment() == over1e->segment()); SkASSERT(over2s->segment() == over2e->segment()); SkASSERT(over1s->segment() == over2s->segment()); SkASSERT(over1s->segment() != coinSeg); SkASSERT(over1s->segment() != oppSeg); SkASSERT(coinSeg != oppSeg); double coinTs, coinTe, oppTs, oppTe; coinTs = TRange(over1s, tStart, coinSeg SkDEBUGPARAMS(over1e)); coinTe = TRange(over1s, tEnd, coinSeg SkDEBUGPARAMS(over1e)); SkOpSpanBase::Collapsed result = coinSeg->collapsed(coinTs, coinTe); if (SkOpSpanBase::Collapsed::kNo != result) { return log->record(SkPathOpsDebug::kAddIfCollapsed_Glitch, coinSeg); } oppTs = TRange(over2s, tStart, oppSeg SkDEBUGPARAMS(over2e)); oppTe = TRange(over2s, tEnd, oppSeg SkDEBUGPARAMS(over2e)); result = oppSeg->collapsed(oppTs, oppTe); if (SkOpSpanBase::Collapsed::kNo != result) { return log->record(SkPathOpsDebug::kAddIfCollapsed_Glitch, oppSeg); } if (coinTs > coinTe) { using std::swap; swap(coinTs, coinTe); swap(oppTs, oppTe); } this->debugAddOrOverlap(log, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, added); return; } /* Commented-out lines keep this in sync addOrOverlap() */ // If this is called by addEndMovedSpans(), a returned false propogates out to an abort. // If this is called by AddIfMissing(), a returned false indicates there was nothing to add void SkOpCoincidence::debugAddOrOverlap(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, double coinTs, double coinTe, double oppTs, double oppTe, bool* added) const { SkTDArray overlaps; SkOPASSERT(!fTop); // this is (correctly) reversed in addifMissing() if (fTop && !this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { return; } if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { return; } const SkCoincidentSpans* overlap = overlaps.size() ? overlaps[0] : nullptr; for (int index = 1; index < overlaps.size(); ++index) { // combine overlaps before continuing const SkCoincidentSpans* test = overlaps[index]; if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->coinPtTStart()); } if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->coinPtTEnd()); } if (overlap->flipped() ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->oppPtTStart()); } if (overlap->flipped() ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->oppPtTEnd()); } if (!fHead) { this->debugRelease(log, fHead, test); this->debugRelease(log, fTop, test); } } const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); RETURN_FALSE_IF(overlap && cs && ce && overlap->contains(cs, ce), coinSeg); RETURN_FALSE_IF(cs != ce || !cs, coinSeg); const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); RETURN_FALSE_IF(overlap && os && oe && overlap->contains(os, oe), oppSeg); SkASSERT(true || !cs || !cs->deleted()); SkASSERT(true || !os || !os->deleted()); SkASSERT(true || !ce || !ce->deleted()); SkASSERT(true || !oe || !oe->deleted()); const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; RETURN_FALSE_IF(csExisting && csExisting == ceExisting, coinSeg); RETURN_FALSE_IF(csExisting && (csExisting == ce || csExisting->contains(ceExisting ? ceExisting : ce)), coinSeg); RETURN_FALSE_IF(ceExisting && (ceExisting == cs || ceExisting->contains(csExisting ? csExisting : cs)), coinSeg); const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; RETURN_FALSE_IF(osExisting && osExisting == oeExisting, oppSeg); RETURN_FALSE_IF(osExisting && (osExisting == oe || osExisting->contains(oeExisting ? oeExisting : oe)), oppSeg); RETURN_FALSE_IF(oeExisting && (oeExisting == os || oeExisting->contains(osExisting ? osExisting : os)), oppSeg); bool csDeleted = false, osDeleted = false, ceDeleted = false, oeDeleted = false; this->debugValidate(); if (!cs || !os) { if (!cs) cs = coinSeg->debugAddT(coinTs, log); if (!os) os = oppSeg->debugAddT(oppTs, log); // RETURN_FALSE_IF(callerAborts, !csWritable || !osWritable); if (cs && os) cs->span()->debugAddOpp(log, os->span()); // cs = csWritable; // os = osWritable->active(); RETURN_FALSE_IF((ce && ce->deleted()) || (oe && oe->deleted()), coinSeg); } if (!ce || !oe) { if (!ce) ce = coinSeg->debugAddT(coinTe, log); if (!oe) oe = oppSeg->debugAddT(oppTe, log); if (ce && oe) ce->span()->debugAddOpp(log, oe->span()); // ce = ceWritable; // oe = oeWritable; } this->debugValidate(); RETURN_FALSE_IF(csDeleted, coinSeg); RETURN_FALSE_IF(osDeleted, oppSeg); RETURN_FALSE_IF(ceDeleted, coinSeg); RETURN_FALSE_IF(oeDeleted, oppSeg); RETURN_FALSE_IF(!cs || !ce || cs == ce || cs->contains(ce) || !os || !oe || os == oe || os->contains(oe), coinSeg); bool result = true; if (overlap) { if (overlap->coinPtTStart()->segment() == coinSeg) { log->record(SkPathOpsDebug::kAddMissingExtend_Glitch, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); } else { if (oppTs > oppTe) { using std::swap; swap(coinTs, coinTe); swap(oppTs, oppTe); } log->record(SkPathOpsDebug::kAddMissingExtend_Glitch, oppSeg, oppTs, oppTe, coinSeg, coinTs, coinTe); } #if 0 && DEBUG_COINCIDENCE_VERBOSE if (result) { overlap->debugShow(); } #endif } else { log->record(SkPathOpsDebug::kAddMissingCoin_Glitch, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); #if 0 && DEBUG_COINCIDENCE_VERBOSE fHead->debugShow(); #endif } this->debugValidate(); return (void) result; } // Extra commented-out lines keep this in sync with addMissing() /* detects overlaps of different coincident runs on same segment */ /* does not detect overlaps for pairs without any segments in common */ // returns true if caller should loop again void SkOpCoincidence::debugAddMissing(SkPathOpsDebug::GlitchLog* log, bool* added) const { const SkCoincidentSpans* outer = fHead; *added = false; if (!outer) { return; } // fTop = outer; // fHead = nullptr; do { // addifmissing can modify the list that this is walking // save head so that walker can iterate over old data unperturbed // addifmissing adds to head freely then add saved head in the end const SkOpPtT* ocs = outer->coinPtTStart(); SkASSERT(!ocs->deleted()); const SkOpSegment* outerCoin = ocs->segment(); SkASSERT(!outerCoin->done()); // if it's done, should have already been removed from list const SkOpPtT* oos = outer->oppPtTStart(); if (oos->deleted()) { return; } const SkOpSegment* outerOpp = oos->segment(); SkASSERT(!outerOpp->done()); // SkOpSegment* outerCoinWritable = const_cast(outerCoin); // SkOpSegment* outerOppWritable = const_cast(outerOpp); const SkCoincidentSpans* inner = outer; while ((inner = inner->next())) { this->debugValidate(); double overS, overE; const SkOpPtT* ics = inner->coinPtTStart(); SkASSERT(!ics->deleted()); const SkOpSegment* innerCoin = ics->segment(); SkASSERT(!innerCoin->done()); const SkOpPtT* ios = inner->oppPtTStart(); SkASSERT(!ios->deleted()); const SkOpSegment* innerOpp = ios->segment(); SkASSERT(!innerOpp->done()); // SkOpSegment* innerCoinWritable = const_cast(innerCoin); // SkOpSegment* innerOppWritable = const_cast(innerOpp); if (outerCoin == innerCoin) { const SkOpPtT* oce = outer->coinPtTEnd(); if (oce->deleted()) { return; } const SkOpPtT* ice = inner->coinPtTEnd(); SkASSERT(!ice->deleted()); if (outerOpp != innerOpp && this->overlap(ocs, oce, ics, ice, &overS, &overE)) { this->debugAddIfMissing(log, ocs->starter(oce), ics->starter(ice), overS, overE, outerOpp, innerOpp, added, ocs->debugEnder(oce), ics->debugEnder(ice)); } } else if (outerCoin == innerOpp) { const SkOpPtT* oce = outer->coinPtTEnd(); SkASSERT(!oce->deleted()); const SkOpPtT* ioe = inner->oppPtTEnd(); SkASSERT(!ioe->deleted()); if (outerOpp != innerCoin && this->overlap(ocs, oce, ios, ioe, &overS, &overE)) { this->debugAddIfMissing(log, ocs->starter(oce), ios->starter(ioe), overS, overE, outerOpp, innerCoin, added, ocs->debugEnder(oce), ios->debugEnder(ioe)); } } else if (outerOpp == innerCoin) { const SkOpPtT* ooe = outer->oppPtTEnd(); SkASSERT(!ooe->deleted()); const SkOpPtT* ice = inner->coinPtTEnd(); SkASSERT(!ice->deleted()); SkASSERT(outerCoin != innerOpp); if (this->overlap(oos, ooe, ics, ice, &overS, &overE)) { this->debugAddIfMissing(log, oos->starter(ooe), ics->starter(ice), overS, overE, outerCoin, innerOpp, added, oos->debugEnder(ooe), ics->debugEnder(ice)); } } else if (outerOpp == innerOpp) { const SkOpPtT* ooe = outer->oppPtTEnd(); SkASSERT(!ooe->deleted()); const SkOpPtT* ioe = inner->oppPtTEnd(); if (ioe->deleted()) { return; } SkASSERT(outerCoin != innerCoin); if (this->overlap(oos, ooe, ios, ioe, &overS, &overE)) { this->debugAddIfMissing(log, oos->starter(ooe), ios->starter(ioe), overS, overE, outerCoin, innerCoin, added, oos->debugEnder(ooe), ios->debugEnder(ioe)); } } this->debugValidate(); } } while ((outer = outer->next())); // this->restoreHead(); return; } // Commented-out lines keep this in sync with release() void SkOpCoincidence::debugRelease(SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* coin, const SkCoincidentSpans* remove) const { const SkCoincidentSpans* head = coin; const SkCoincidentSpans* prev = nullptr; const SkCoincidentSpans* next; do { next = coin->next(); if (coin == remove) { if (prev) { // prev->setNext(next); } else if (head == fHead) { // fHead = next; } else { // fTop = next; } log->record(SkPathOpsDebug::kReleasedSpan_Glitch, coin); } prev = coin; } while ((coin = next)); return; } void SkOpCoincidence::debugRelease(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* deleted) const { const SkCoincidentSpans* coin = fHead; if (!coin) { return; } do { if (coin->coinPtTStart()->segment() == deleted || coin->coinPtTEnd()->segment() == deleted || coin->oppPtTStart()->segment() == deleted || coin->oppPtTEnd()->segment() == deleted) { log->record(SkPathOpsDebug::kReleasedSpan_Glitch, coin); } } while ((coin = coin->next())); } // Commented-out lines keep this in sync with expand() // expand the range by checking adjacent spans for coincidence bool SkOpCoincidence::debugExpand(SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* coin = fHead; if (!coin) { return false; } bool expanded = false; do { if (coin->debugExpand(log)) { // check to see if multiple spans expanded so they are now identical const SkCoincidentSpans* test = fHead; do { if (coin == test) { continue; } if (coin->coinPtTStart() == test->coinPtTStart() && coin->oppPtTStart() == test->oppPtTStart()) { if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, fHead, test->coinPtTStart()); break; } } while ((test = test->next())); expanded = true; } } while ((coin = coin->next())); return expanded; } // Commented-out lines keep this in sync with mark() /* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ void SkOpCoincidence::debugMark(SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* coin = fHead; if (!coin) { return; } do { FAIL_IF_COIN(!coin->coinPtTStartWritable()->span()->upCastable(), coin); const SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); // SkASSERT(start->deleted()); const SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); // SkASSERT(end->deleted()); const SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); // SkASSERT(oStart->deleted()); const SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); // SkASSERT(oEnd->deleted()); bool flipped = coin->flipped(); if (flipped) { using std::swap; swap(oStart, oEnd); } /* coin and opp spans may not match up. Mark the ends, and then let the interior get marked as many times as the spans allow */ start->debugInsertCoincidence(log, oStart->upCast()); end->debugInsertCoinEnd(log, oEnd); const SkOpSegment* segment = start->segment(); const SkOpSegment* oSegment = oStart->segment(); const SkOpSpanBase* next = start; const SkOpSpanBase* oNext = oStart; bool ordered; FAIL_IF_COIN(!coin->ordered(&ordered), coin); while ((next = next->upCast()->next()) != end) { FAIL_IF_COIN(!next->upCastable(), coin); next->upCast()->debugInsertCoincidence(log, oSegment, flipped, ordered); } while ((oNext = oNext->upCast()->next()) != oEnd) { FAIL_IF_COIN(!oNext->upCastable(), coin); oNext->upCast()->debugInsertCoincidence(log, segment, flipped, ordered); } } while ((coin = coin->next())); return; } #endif // DEBUG_COIN #if DEBUG_COIN // Commented-out lines keep this in sync with markCollapsed() void SkOpCoincidence::debugMarkCollapsed(SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* coin, const SkOpPtT* test) const { const SkCoincidentSpans* head = coin; while (coin) { if (coin->collapsed(test)) { if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { log->record(SkPathOpsDebug::kCollapsedCoin_Glitch, coin); } if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { log->record(SkPathOpsDebug::kCollapsedCoin_Glitch, coin); } this->debugRelease(log, head, coin); } coin = coin->next(); } } // Commented-out lines keep this in sync with markCollapsed() void SkOpCoincidence::debugMarkCollapsed(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* test) const { this->debugMarkCollapsed(log, fHead, test); this->debugMarkCollapsed(log, fTop, test); } #endif // DEBUG_COIN void SkCoincidentSpans::debugShow() const { SkDebugf("coinSpan - id=%d t=%1.9g tEnd=%1.9g\n", coinPtTStart()->segment()->debugID(), coinPtTStart()->fT, coinPtTEnd()->fT); SkDebugf("coinSpan + id=%d t=%1.9g tEnd=%1.9g\n", oppPtTStart()->segment()->debugID(), oppPtTStart()->fT, oppPtTEnd()->fT); } void SkOpCoincidence::debugShowCoincidence() const { #if DEBUG_COINCIDENCE const SkCoincidentSpans* span = fHead; while (span) { span->debugShow(); span = span->next(); } #endif // DEBUG_COINCIDENCE } #if DEBUG_COIN static void DebugCheckBetween(const SkOpSpanBase* next, const SkOpSpanBase* end, double oStart, double oEnd, const SkOpSegment* oSegment, SkPathOpsDebug::GlitchLog* log) { SkASSERT(next != end); SkASSERT(!next->contains(end) || log); if (next->t() > end->t()) { using std::swap; swap(next, end); } do { const SkOpPtT* ptT = next->ptT(); int index = 0; bool somethingBetween = false; do { ++index; ptT = ptT->next(); const SkOpPtT* checkPtT = next->ptT(); if (ptT == checkPtT) { break; } bool looped = false; for (int check = 0; check < index; ++check) { if ((looped = checkPtT == ptT)) { break; } checkPtT = checkPtT->next(); } if (looped) { SkASSERT(0); break; } if (ptT->deleted()) { continue; } if (ptT->segment() != oSegment) { continue; } somethingBetween |= between(oStart, ptT->fT, oEnd); } while (true); SkASSERT(somethingBetween); } while (next != end && (next = next->upCast()->next())); } static void DebugCheckOverlap(const SkCoincidentSpans* test, const SkCoincidentSpans* list, SkPathOpsDebug::GlitchLog* log) { if (!list) { return; } const SkOpSegment* coinSeg = test->coinPtTStart()->segment(); SkASSERT(coinSeg == test->coinPtTEnd()->segment()); const SkOpSegment* oppSeg = test->oppPtTStart()->segment(); SkASSERT(oppSeg == test->oppPtTEnd()->segment()); SkASSERT(coinSeg != test->oppPtTStart()->segment()); SkDEBUGCODE(double tcs = test->coinPtTStart()->fT); SkASSERT(between(0, tcs, 1)); SkDEBUGCODE(double tce = test->coinPtTEnd()->fT); SkASSERT(between(0, tce, 1)); SkASSERT(tcs < tce); double tos = test->oppPtTStart()->fT; SkASSERT(between(0, tos, 1)); double toe = test->oppPtTEnd()->fT; SkASSERT(between(0, toe, 1)); SkASSERT(tos != toe); if (tos > toe) { using std::swap; swap(tos, toe); } do { double lcs, lce, los, loe; if (coinSeg == list->coinPtTStart()->segment()) { if (oppSeg != list->oppPtTStart()->segment()) { continue; } lcs = list->coinPtTStart()->fT; lce = list->coinPtTEnd()->fT; los = list->oppPtTStart()->fT; loe = list->oppPtTEnd()->fT; if (los > loe) { using std::swap; swap(los, loe); } } else if (coinSeg == list->oppPtTStart()->segment()) { if (oppSeg != list->coinPtTStart()->segment()) { continue; } lcs = list->oppPtTStart()->fT; lce = list->oppPtTEnd()->fT; if (lcs > lce) { using std::swap; swap(lcs, lce); } los = list->coinPtTStart()->fT; loe = list->coinPtTEnd()->fT; } else { continue; } SkASSERT(tce < lcs || lce < tcs); SkASSERT(toe < los || loe < tos); } while ((list = list->next())); } static void DebugCheckOverlapTop(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, SkPathOpsDebug::GlitchLog* log) { // check for overlapping coincident spans const SkCoincidentSpans* test = head; while (test) { const SkCoincidentSpans* next = test->next(); DebugCheckOverlap(test, next, log); DebugCheckOverlap(test, opt, log); test = next; } } static void DebugValidate(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, SkPathOpsDebug::GlitchLog* log) { // look for pts inside coincident spans that are not inside the opposite spans const SkCoincidentSpans* coin = head; while (coin) { SkASSERT(SkOpCoincidence::Ordered(coin->coinPtTStart()->segment(), coin->oppPtTStart()->segment())); SkASSERT(coin->coinPtTStart()->span()->ptT() == coin->coinPtTStart()); SkASSERT(coin->coinPtTEnd()->span()->ptT() == coin->coinPtTEnd()); SkASSERT(coin->oppPtTStart()->span()->ptT() == coin->oppPtTStart()); SkASSERT(coin->oppPtTEnd()->span()->ptT() == coin->oppPtTEnd()); coin = coin->next(); } DebugCheckOverlapTop(head, opt, log); } #endif // DEBUG_COIN void SkOpCoincidence::debugValidate() const { #if DEBUG_COINCIDENCE DebugValidate(fHead, fTop, nullptr); DebugValidate(fTop, nullptr, nullptr); #endif } #if DEBUG_COIN static void DebugCheckBetween(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, SkPathOpsDebug::GlitchLog* log) { // look for pts inside coincident spans that are not inside the opposite spans const SkCoincidentSpans* coin = head; while (coin) { DebugCheckBetween(coin->coinPtTStart()->span(), coin->coinPtTEnd()->span(), coin->oppPtTStart()->fT, coin->oppPtTEnd()->fT, coin->oppPtTStart()->segment(), log); DebugCheckBetween(coin->oppPtTStart()->span(), coin->oppPtTEnd()->span(), coin->coinPtTStart()->fT, coin->coinPtTEnd()->fT, coin->coinPtTStart()->segment(), log); coin = coin->next(); } DebugCheckOverlapTop(head, opt, log); } #endif void SkOpCoincidence::debugCheckBetween() const { #if DEBUG_COINCIDENCE if (fGlobalState->debugCheckHealth()) { return; } DebugCheckBetween(fHead, fTop, nullptr); DebugCheckBetween(fTop, nullptr, nullptr); #endif } #if DEBUG_COIN void SkOpContour::debugCheckHealth(SkPathOpsDebug::GlitchLog* log) const { const SkOpSegment* segment = &fHead; do { segment->debugCheckHealth(log); } while ((segment = segment->next())); } void SkOpCoincidence::debugCheckValid(SkPathOpsDebug::GlitchLog* log) const { #if DEBUG_VALIDATE DebugValidate(fHead, fTop, log); DebugValidate(fTop, nullptr, log); #endif } void SkOpCoincidence::debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const { const SkCoincidentSpans* coin = fHead; if (!coin) { return; } do { coin->debugCorrectEnds(log); } while ((coin = coin->next())); } // commmented-out lines keep this aligned with missingCoincidence() void SkOpContour::debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const { // SkASSERT(fCount > 0); const SkOpSegment* segment = &fHead; // bool result = false; do { segment->debugMissingCoincidence(log); segment = segment->next(); } while (segment); return; } void SkOpContour::debugMoveMultiples(SkPathOpsDebug::GlitchLog* log) const { SkASSERT(fCount > 0); const SkOpSegment* segment = &fHead; do { segment->debugMoveMultiples(log); } while ((segment = segment->next())); return; } void SkOpContour::debugMoveNearby(SkPathOpsDebug::GlitchLog* log) const { SkASSERT(fCount > 0); const SkOpSegment* segment = &fHead; do { segment->debugMoveNearby(log); } while ((segment = segment->next())); } #endif #if DEBUG_COINCIDENCE_ORDER void SkOpSegment::debugResetCoinT() const { fDebugBaseIndex = -1; fDebugBaseMin = 1; fDebugBaseMax = -1; fDebugLastIndex = -1; fDebugLastMin = 1; fDebugLastMax = -1; } #endif void SkOpSegment::debugValidate() const { #if DEBUG_COINCIDENCE_ORDER { const SkOpSpanBase* span = &fHead; do { span->debugResetCoinT(); } while (!span->final() && (span = span->upCast()->next())); span = &fHead; int index = 0; do { span->debugSetCoinT(index++); } while (!span->final() && (span = span->upCast()->next())); } #endif #if DEBUG_COINCIDENCE if (this->globalState()->debugCheckHealth()) { return; } #endif #if DEBUG_VALIDATE const SkOpSpanBase* span = &fHead; double lastT = -1; const SkOpSpanBase* prev = nullptr; int count = 0; int done = 0; do { if (!span->final()) { ++count; done += span->upCast()->done() ? 1 : 0; } SkASSERT(span->segment() == this); SkASSERT(!prev || prev->upCast()->next() == span); SkASSERT(!prev || prev == span->prev()); prev = span; double t = span->ptT()->fT; SkASSERT(lastT < t); lastT = t; span->debugValidate(); } while (!span->final() && (span = span->upCast()->next())); SkASSERT(count == fCount); SkASSERT(done == fDoneCount); SkASSERT(count >= fDoneCount); SkASSERT(span->final()); span->debugValidate(); #endif } #if DEBUG_COIN // Commented-out lines keep this in sync with addOpp() void SkOpSpanBase::debugAddOpp(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp) const { const SkOpPtT* oppPrev = this->ptT()->oppPrev(opp->ptT()); if (!oppPrev) { return; } this->debugMergeMatches(log, opp); this->ptT()->debugAddOpp(opp->ptT(), oppPrev); this->debugCheckForCollapsedCoincidence(log); } // Commented-out lines keep this in sync with checkForCollapsedCoincidence() void SkOpSpanBase::debugCheckForCollapsedCoincidence(SkPathOpsDebug::GlitchLog* log) const { const SkOpCoincidence* coins = this->globalState()->coincidence(); if (coins->isEmpty()) { return; } // the insert above may have put both ends of a coincident run in the same span // for each coincident ptT in loop; see if its opposite in is also in the loop // this implementation is the motivation for marking that a ptT is referenced by a coincident span const SkOpPtT* head = this->ptT(); const SkOpPtT* test = head; do { if (!test->coincident()) { continue; } coins->debugMarkCollapsed(log, test); } while ((test = test->next()) != head); } #endif bool SkOpSpanBase::debugCoinEndLoopCheck() const { int loop = 0; const SkOpSpanBase* next = this; SkOpSpanBase* nextCoin; do { nextCoin = next->fCoinEnd; SkASSERT(nextCoin == this || nextCoin->fCoinEnd != nextCoin); for (int check = 1; check < loop - 1; ++check) { const SkOpSpanBase* checkCoin = this->fCoinEnd; const SkOpSpanBase* innerCoin = checkCoin; for (int inner = check + 1; inner < loop; ++inner) { innerCoin = innerCoin->fCoinEnd; if (checkCoin == innerCoin) { SkDebugf("*** bad coincident end loop ***\n"); return false; } } } ++loop; } while ((next = nextCoin) && next != this); return true; } #if DEBUG_COIN // Commented-out lines keep this in sync with insertCoinEnd() void SkOpSpanBase::debugInsertCoinEnd(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* coin) const { if (containsCoinEnd(coin)) { // SkASSERT(coin->containsCoinEnd(this)); return; } debugValidate(); // SkASSERT(this != coin); log->record(SkPathOpsDebug::kMarkCoinEnd_Glitch, this, coin); // coin->fCoinEnd = this->fCoinEnd; // this->fCoinEnd = coinNext; debugValidate(); } // Commented-out lines keep this in sync with mergeMatches() // Look to see if pt-t linked list contains same segment more than once // if so, and if each pt-t is directly pointed to by spans in that segment, // merge them // keep the points, but remove spans so that the segment doesn't have 2 or more // spans pointing to the same pt-t loop at different loop elements void SkOpSpanBase::debugMergeMatches(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp) const { const SkOpPtT* test = &fPtT; const SkOpPtT* testNext; const SkOpPtT* stop = test; do { testNext = test->next(); if (test->deleted()) { continue; } const SkOpSpanBase* testBase = test->span(); SkASSERT(testBase->ptT() == test); const SkOpSegment* segment = test->segment(); if (segment->done()) { continue; } const SkOpPtT* inner = opp->ptT(); const SkOpPtT* innerStop = inner; do { if (inner->segment() != segment) { continue; } if (inner->deleted()) { continue; } const SkOpSpanBase* innerBase = inner->span(); SkASSERT(innerBase->ptT() == inner); // when the intersection is first detected, the span base is marked if there are // more than one point in the intersection. // if (!innerBase->hasMultipleHint() && !testBase->hasMultipleHint()) { if (!zero_or_one(inner->fT)) { log->record(SkPathOpsDebug::kMergeMatches_Glitch, innerBase, test); } else { SkASSERT(inner->fT != test->fT); if (!zero_or_one(test->fT)) { log->record(SkPathOpsDebug::kMergeMatches_Glitch, testBase, inner); } else { log->record(SkPathOpsDebug::kMergeMatches_Glitch, segment); // SkDEBUGCODE(testBase->debugSetDeleted()); // test->setDeleted(); // SkDEBUGCODE(innerBase->debugSetDeleted()); // inner->setDeleted(); } } #ifdef SK_DEBUG // assert if another undeleted entry points to segment const SkOpPtT* debugInner = inner; while ((debugInner = debugInner->next()) != innerStop) { if (debugInner->segment() != segment) { continue; } if (debugInner->deleted()) { continue; } SkOPASSERT(0); } #endif break; // } break; } while ((inner = inner->next()) != innerStop); } while ((test = testNext) != stop); this->debugCheckForCollapsedCoincidence(log); } #endif void SkOpSpanBase::debugResetCoinT() const { #if DEBUG_COINCIDENCE_ORDER const SkOpPtT* ptT = &fPtT; do { ptT->debugResetCoinT(); ptT = ptT->next(); } while (ptT != &fPtT); #endif } void SkOpSpanBase::debugSetCoinT(int index) const { #if DEBUG_COINCIDENCE_ORDER const SkOpPtT* ptT = &fPtT; do { if (!ptT->deleted()) { ptT->debugSetCoinT(index); } ptT = ptT->next(); } while (ptT != &fPtT); #endif } const SkOpSpan* SkOpSpanBase::debugStarter(SkOpSpanBase const** endPtr) const { const SkOpSpanBase* end = *endPtr; SkASSERT(this->segment() == end->segment()); const SkOpSpanBase* result; if (t() < end->t()) { result = this; } else { result = end; *endPtr = this; } return result->upCast(); } void SkOpSpanBase::debugValidate() const { #if DEBUG_COINCIDENCE if (this->globalState()->debugCheckHealth()) { return; } #endif #if DEBUG_VALIDATE const SkOpPtT* ptT = &fPtT; SkASSERT(ptT->span() == this); do { // SkASSERT(SkDPoint::RoughlyEqual(fPtT.fPt, ptT->fPt)); ptT->debugValidate(); ptT = ptT->next(); } while (ptT != &fPtT); SkASSERT(this->debugCoinEndLoopCheck()); if (!this->final()) { SkASSERT(this->upCast()->debugCoinLoopCheck()); } if (fFromAngle) { fFromAngle->debugValidate(); } if (!this->final() && this->upCast()->toAngle()) { this->upCast()->toAngle()->debugValidate(); } #endif } bool SkOpSpan::debugCoinLoopCheck() const { int loop = 0; const SkOpSpan* next = this; SkOpSpan* nextCoin; do { nextCoin = next->fCoincident; SkASSERT(nextCoin == this || nextCoin->fCoincident != nextCoin); for (int check = 1; check < loop - 1; ++check) { const SkOpSpan* checkCoin = this->fCoincident; const SkOpSpan* innerCoin = checkCoin; for (int inner = check + 1; inner < loop; ++inner) { innerCoin = innerCoin->fCoincident; if (checkCoin == innerCoin) { SkDebugf("*** bad coincident loop ***\n"); return false; } } } ++loop; } while ((next = nextCoin) && next != this); return true; } #if DEBUG_COIN // Commented-out lines keep this in sync with insertCoincidence() in header void SkOpSpan::debugInsertCoincidence(SkPathOpsDebug::GlitchLog* log, const SkOpSpan* coin) const { if (containsCoincidence(coin)) { // SkASSERT(coin->containsCoincidence(this)); return; } debugValidate(); // SkASSERT(this != coin); log->record(SkPathOpsDebug::kMarkCoinStart_Glitch, this, coin); // coin->fCoincident = this->fCoincident; // this->fCoincident = coinNext; debugValidate(); } // Commented-out lines keep this in sync with insertCoincidence() void SkOpSpan::debugInsertCoincidence(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* segment, bool flipped, bool ordered) const { if (this->containsCoincidence(segment)) { return; } const SkOpPtT* next = &fPtT; while ((next = next->next()) != &fPtT) { if (next->segment() == segment) { const SkOpSpan* span; const SkOpSpanBase* base = next->span(); if (!ordered) { const SkOpSpanBase* spanEnd = fNext->contains(segment)->span(); const SkOpPtT* start = base->ptT()->starter(spanEnd->ptT()); FAIL_IF_COIN(!start->span()->upCastable(), this); span = const_cast(start->span()->upCast()); } else if (flipped) { span = base->prev(); FAIL_IF_COIN(!span, this); } else { FAIL_IF_COIN(!base->upCastable(), this); span = base->upCast(); } log->record(SkPathOpsDebug::kMarkCoinInsert_Glitch, span); return; } } log->record(SkPathOpsDebug::kMarkCoinMissing_Glitch, segment, this); return; } #endif // DEBUG_COIN // called only by test code int SkIntersections::debugCoincidentUsed() const { if (!fIsCoincident[0]) { SkASSERT(!fIsCoincident[1]); return 0; } int count = 0; SkDEBUGCODE(int count2 = 0;) for (int index = 0; index < fUsed; ++index) { if (fIsCoincident[0] & (1 << index)) { ++count; } #ifdef SK_DEBUG if (fIsCoincident[1] & (1 << index)) { ++count2; } #endif } SkASSERT(count == count2); return count; } // Commented-out lines keep this in sync with addOpp() void SkOpPtT::debugAddOpp(const SkOpPtT* opp, const SkOpPtT* oppPrev) const { SkDEBUGCODE(const SkOpPtT* oldNext = this->fNext); SkASSERT(this != opp); // this->fNext = opp; SkASSERT(oppPrev != oldNext); // oppPrev->fNext = oldNext; } bool SkOpPtT::debugContains(const SkOpPtT* check) const { SkASSERT(this != check); const SkOpPtT* ptT = this; int links = 0; do { ptT = ptT->next(); if (ptT == check) { return true; } ++links; const SkOpPtT* test = this; for (int index = 0; index < links; ++index) { if (ptT == test) { return false; } test = test->next(); } } while (true); } const SkOpPtT* SkOpPtT::debugContains(const SkOpSegment* check) const { SkASSERT(this->segment() != check); const SkOpPtT* ptT = this; int links = 0; do { ptT = ptT->next(); if (ptT->segment() == check) { return ptT; } ++links; const SkOpPtT* test = this; for (int index = 0; index < links; ++index) { if (ptT == test) { return nullptr; } test = test->next(); } } while (true); } const SkOpPtT* SkOpPtT::debugEnder(const SkOpPtT* end) const { return fT < end->fT ? end : this; } int SkOpPtT::debugLoopLimit(bool report) const { int loop = 0; const SkOpPtT* next = this; do { for (int check = 1; check < loop - 1; ++check) { const SkOpPtT* checkPtT = this->fNext; const SkOpPtT* innerPtT = checkPtT; for (int inner = check + 1; inner < loop; ++inner) { innerPtT = innerPtT->fNext; if (checkPtT == innerPtT) { if (report) { SkDebugf("*** bad ptT loop ***\n"); } return loop; } } } // there's nothing wrong with extremely large loop counts -- but this may appear to hang // by taking a very long time to figure out that no loop entry is a duplicate // -- and it's likely that a large loop count is indicative of a bug somewhere if (++loop > 1000) { SkDebugf("*** loop count exceeds 1000 ***\n"); return 1000; } } while ((next = next->fNext) && next != this); return 0; } const SkOpPtT* SkOpPtT::debugOppPrev(const SkOpPtT* opp) const { return this->oppPrev(const_cast(opp)); } void SkOpPtT::debugResetCoinT() const { #if DEBUG_COINCIDENCE_ORDER this->segment()->debugResetCoinT(); #endif } void SkOpPtT::debugSetCoinT(int index) const { #if DEBUG_COINCIDENCE_ORDER this->segment()->debugSetCoinT(index, fT); #endif } void SkOpPtT::debugValidate() const { #if DEBUG_COINCIDENCE if (this->globalState()->debugCheckHealth()) { return; } #endif #if DEBUG_VALIDATE SkOpPhase phase = contour()->globalState()->phase(); if (phase == SkOpPhase::kIntersecting || phase == SkOpPhase::kFixWinding) { return; } SkASSERT(fNext); SkASSERT(fNext != this); SkASSERT(fNext->fNext); SkASSERT(debugLoopLimit(false) == 0); #endif } static void output_scalar(SkScalar num) { if (num == (int) num) { SkDebugf("%d", (int) num); } else { SkString str; str.printf("%1.9g", num); int width = (int) str.size(); const char* cStr = str.c_str(); while (cStr[width - 1] == '0') { --width; } str.resize(width); SkDebugf("%sf", str.c_str()); } } static void output_points(const SkPoint* pts, int count) { for (int index = 0; index < count; ++index) { output_scalar(pts[index].fX); SkDebugf(", "); output_scalar(pts[index].fY); if (index + 1 < count) { SkDebugf(", "); } } } static void showPathContours(const SkPath& path, const char* pathName) { for (auto [verb, pts, w] : SkPathPriv::Iterate(path)) { switch (verb) { case SkPathVerb::kMove: SkDebugf(" %s.moveTo(", pathName); output_points(&pts[0], 1); SkDebugf(");\n"); continue; case SkPathVerb::kLine: SkDebugf(" %s.lineTo(", pathName); output_points(&pts[1], 1); SkDebugf(");\n"); break; case SkPathVerb::kQuad: SkDebugf(" %s.quadTo(", pathName); output_points(&pts[1], 2); SkDebugf(");\n"); break; case SkPathVerb::kConic: SkDebugf(" %s.conicTo(", pathName); output_points(&pts[1], 2); SkDebugf(", %1.9gf);\n", *w); break; case SkPathVerb::kCubic: SkDebugf(" %s.cubicTo(", pathName); output_points(&pts[1], 3); SkDebugf(");\n"); break; case SkPathVerb::kClose: SkDebugf(" %s.close();\n", pathName); break; default: SkDEBUGFAIL("bad verb"); return; } } } static const char* gFillTypeStr[] = { "kWinding", "kEvenOdd", "kInverseWinding", "kInverseEvenOdd" }; void SkPathOpsDebug::ShowOnePath(const SkPath& path, const char* name, bool includeDeclaration) { #define SUPPORT_RECT_CONTOUR_DETECTION 0 #if SUPPORT_RECT_CONTOUR_DETECTION int rectCount = path.isRectContours() ? path.rectContours(nullptr, nullptr) : 0; if (rectCount > 0) { SkTDArray rects; SkTDArray directions; rects.setCount(rectCount); directions.setCount(rectCount); path.rectContours(rects.begin(), directions.begin()); for (int contour = 0; contour < rectCount; ++contour) { const SkRect& rect = rects[contour]; SkDebugf("path.addRect(%1.9g, %1.9g, %1.9g, %1.9g, %s);\n", rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, directions[contour] == SkPathDirection::kCCW ? "SkPathDirection::kCCW" : "SkPathDirection::kCW"); } return; } #endif SkPathFillType fillType = path.getFillType(); SkASSERT(fillType >= SkPathFillType::kWinding && fillType <= SkPathFillType::kInverseEvenOdd); if (includeDeclaration) { SkDebugf(" SkPath %s;\n", name); } SkDebugf(" %s.setFillType(SkPath::%s);\n", name, gFillTypeStr[(int)fillType]); showPathContours(path, name); } #if DEBUG_DUMP_VERIFY static void dump_path(FILE* file, const SkPath& path, bool dumpAsHex) { SkDynamicMemoryWStream wStream; path.dump(&wStream, dumpAsHex); sk_sp data(wStream.detachAsData()); fprintf(file, "%.*s\n", (int) data->size(), (char*) data->data()); } static int dumpID = 0; void DumpOp(const SkPath& one, const SkPath& two, SkPathOp op, const char* testName) { FILE* file = sk_fopen("op_dump.txt", kWrite_SkFILE_Flag); DumpOp(file, one, two, op, testName); } void DumpOp(FILE* file, const SkPath& one, const SkPath& two, SkPathOp op, const char* testName) { const char* name = testName ? testName : "op"; fprintf(file, "\nstatic void %s_%d(skiatest::Reporter* reporter, const char* filename) {\n", name, ++dumpID); fprintf(file, " SkPath path;\n"); fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", one.getFillType()); dump_path(file, one, true); fprintf(file, " SkPath path1(path);\n"); fprintf(file, " path.reset();\n"); fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", two.getFillType()); dump_path(file, two, true); fprintf(file, " SkPath path2(path);\n"); fprintf(file, " testPathOp(reporter, path1, path2, (SkPathOp) %d, filename);\n", op); fprintf(file, "}\n\n"); fclose(file); } void DumpSimplify(const SkPath& path, const char* testName) { FILE* file = sk_fopen("simplify_dump.txt", kWrite_SkFILE_Flag); DumpSimplify(file, path, testName); } void DumpSimplify(FILE* file, const SkPath& path, const char* testName) { const char* name = testName ? testName : "simplify"; fprintf(file, "\nstatic void %s_%d(skiatest::Reporter* reporter, const char* filename) {\n", name, ++dumpID); fprintf(file, " SkPath path;\n"); fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", path.getFillType()); dump_path(file, path, true); fprintf(file, " testSimplify(reporter, path, filename);\n"); fprintf(file, "}\n\n"); fclose(file); } const int bitWidth = 64; const int bitHeight = 64; static void debug_scale_matrix(const SkPath& one, const SkPath* two, SkMatrix& scale) { SkRect larger = one.getBounds(); if (two) { larger.join(two->getBounds()); } SkScalar largerWidth = larger.width(); if (largerWidth < 4) { largerWidth = 4; } SkScalar largerHeight = larger.height(); if (largerHeight < 4) { largerHeight = 4; } SkScalar hScale = (bitWidth - 2) / largerWidth; SkScalar vScale = (bitHeight - 2) / largerHeight; scale.reset(); scale.preScale(hScale, vScale); larger.fLeft *= hScale; larger.fRight *= hScale; larger.fTop *= vScale; larger.fBottom *= vScale; SkScalar dx = -16000 > larger.fLeft ? -16000 - larger.fLeft : 16000 < larger.fRight ? 16000 - larger.fRight : 0; SkScalar dy = -16000 > larger.fTop ? -16000 - larger.fTop : 16000 < larger.fBottom ? 16000 - larger.fBottom : 0; scale.preTranslate(dx, dy); } static int debug_paths_draw_the_same(const SkPath& one, const SkPath& two, SkBitmap& bits) { if (bits.width() == 0) { bits.allocN32Pixels(bitWidth * 2, bitHeight); } SkCanvas canvas(bits); canvas.drawColor(SK_ColorWHITE); SkPaint paint; canvas.save(); const SkRect& bounds1 = one.getBounds(); canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1); canvas.drawPath(one, paint); canvas.restore(); canvas.save(); canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1); canvas.drawPath(two, paint); canvas.restore(); int errors = 0; for (int y = 0; y < bitHeight - 1; ++y) { uint32_t* addr1 = bits.getAddr32(0, y); uint32_t* addr2 = bits.getAddr32(0, y + 1); uint32_t* addr3 = bits.getAddr32(bitWidth, y); uint32_t* addr4 = bits.getAddr32(bitWidth, y + 1); for (int x = 0; x < bitWidth - 1; ++x) { // count 2x2 blocks bool err = addr1[x] != addr3[x]; if (err) { errors += addr1[x + 1] != addr3[x + 1] && addr2[x] != addr4[x] && addr2[x + 1] != addr4[x + 1]; } } } return errors; } void ReportOpFail(const SkPath& one, const SkPath& two, SkPathOp op) { SkDebugf("// Op did not expect failure\n"); DumpOp(stderr, one, two, op, "opTest"); fflush(stderr); } void VerifyOp(const SkPath& one, const SkPath& two, SkPathOp op, const SkPath& result) { SkPath pathOut, scaledPathOut; SkRegion rgnA, rgnB, openClip, rgnOut; openClip.setRect({-16000, -16000, 16000, 16000}); rgnA.setPath(one, openClip); rgnB.setPath(two, openClip); rgnOut.op(rgnA, rgnB, (SkRegion::Op) op); rgnOut.getBoundaryPath(&pathOut); SkMatrix scale; debug_scale_matrix(one, &two, scale); SkRegion scaledRgnA, scaledRgnB, scaledRgnOut; SkPath scaledA, scaledB; scaledA.addPath(one, scale); scaledA.setFillType(one.getFillType()); scaledB.addPath(two, scale); scaledB.setFillType(two.getFillType()); scaledRgnA.setPath(scaledA, openClip); scaledRgnB.setPath(scaledB, openClip); scaledRgnOut.op(scaledRgnA, scaledRgnB, (SkRegion::Op) op); scaledRgnOut.getBoundaryPath(&scaledPathOut); SkBitmap bitmap; SkPath scaledOut; scaledOut.addPath(result, scale); scaledOut.setFillType(result.getFillType()); int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap); const int MAX_ERRORS = 9; if (errors > MAX_ERRORS) { fprintf(stderr, "// Op did not expect errors=%d\n", errors); DumpOp(stderr, one, two, op, "opTest"); fflush(stderr); } } void ReportSimplifyFail(const SkPath& path) { SkDebugf("// Simplify did not expect failure\n"); DumpSimplify(stderr, path, "simplifyTest"); fflush(stderr); } void VerifySimplify(const SkPath& path, const SkPath& result) { SkPath pathOut, scaledPathOut; SkRegion rgnA, openClip, rgnOut; openClip.setRect({-16000, -16000, 16000, 16000}); rgnA.setPath(path, openClip); rgnOut.getBoundaryPath(&pathOut); SkMatrix scale; debug_scale_matrix(path, nullptr, scale); SkRegion scaledRgnA; SkPath scaledA; scaledA.addPath(path, scale); scaledA.setFillType(path.getFillType()); scaledRgnA.setPath(scaledA, openClip); scaledRgnA.getBoundaryPath(&scaledPathOut); SkBitmap bitmap; SkPath scaledOut; scaledOut.addPath(result, scale); scaledOut.setFillType(result.getFillType()); int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap); const int MAX_ERRORS = 9; if (errors > MAX_ERRORS) { fprintf(stderr, "// Simplify did not expect errors=%d\n", errors); DumpSimplify(stderr, path, "simplifyTest"); fflush(stderr); } } #endif // DEBUG_DUMP_VERIFY // global path dumps for msvs Visual Studio 17 to use from Immediate Window void Dump(const SkPath& path) { path.dump(); } void DumpHex(const SkPath& path) { path.dumpHex(); } SkDPoint SkDLine::ptAtT(double t) const { if (0 == t) { return fPts[0]; } if (1 == t) { return fPts[1]; } double one_t = 1 - t; SkDPoint result = { one_t * fPts[0].fX + t * fPts[1].fX, one_t * fPts[0].fY + t * fPts[1].fY }; return result; } double SkDLine::exactPoint(const SkDPoint& xy) const { if (xy == fPts[0]) { // do cheapest test first return 0; } if (xy == fPts[1]) { return 1; } return -1; } double SkDLine::nearPoint(const SkDPoint& xy, bool* unequal) const { if (!AlmostBetweenUlps(fPts[0].fX, xy.fX, fPts[1].fX) || !AlmostBetweenUlps(fPts[0].fY, xy.fY, fPts[1].fY)) { return -1; } // project a perpendicular ray from the point to the line; find the T on the line SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay SkDVector ab0 = xy - fPts[0]; double numer = len.fX * ab0.fX + ab0.fY * len.fY; if (!between(0, numer, denom)) { return -1; } if (!denom) { return 0; } double t = numer / denom; SkDPoint realPt = ptAtT(t); double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? // find the ordinal in the original line with the largest unsigned exponent double tiniest = std::min(std::min(std::min(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); double largest = std::max(std::max(std::max(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); largest = std::max(largest, -tiniest); if (!AlmostEqualUlps_Pin(largest, largest + dist)) { // is the dist within ULPS tolerance? return -1; } if (unequal) { *unequal = (float) largest != (float) (largest + dist); } t = SkPinT(t); // a looser pin breaks skpwww_lptemp_com_3 SkASSERT(between(0, t, 1)); return t; } bool SkDLine::nearRay(const SkDPoint& xy) const { // project a perpendicular ray from the point to the line; find the T on the line SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay SkDVector ab0 = xy - fPts[0]; double numer = len.fX * ab0.fX + ab0.fY * len.fY; double t = numer / denom; SkDPoint realPt = ptAtT(t); double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? // find the ordinal in the original line with the largest unsigned exponent double tiniest = std::min(std::min(std::min(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); double largest = std::max(std::max(std::max(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); largest = std::max(largest, -tiniest); return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? } double SkDLine::ExactPointH(const SkDPoint& xy, double left, double right, double y) { if (xy.fY == y) { if (xy.fX == left) { return 0; } if (xy.fX == right) { return 1; } } return -1; } double SkDLine::NearPointH(const SkDPoint& xy, double left, double right, double y) { if (!AlmostBequalUlps(xy.fY, y)) { return -1; } if (!AlmostBetweenUlps(left, xy.fX, right)) { return -1; } double t = (xy.fX - left) / (right - left); t = SkPinT(t); SkASSERT(between(0, t, 1)); double realPtX = (1 - t) * left + t * right; SkDVector distU = {xy.fY - y, xy.fX - realPtX}; double distSq = distU.fX * distU.fX + distU.fY * distU.fY; double dist = sqrt(distSq); // OPTIMIZATION: can we compare against distSq instead ? double tiniest = std::min(std::min(y, left), right); double largest = std::max(std::max(y, left), right); largest = std::max(largest, -tiniest); if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? return -1; } return t; } double SkDLine::ExactPointV(const SkDPoint& xy, double top, double bottom, double x) { if (xy.fX == x) { if (xy.fY == top) { return 0; } if (xy.fY == bottom) { return 1; } } return -1; } double SkDLine::NearPointV(const SkDPoint& xy, double top, double bottom, double x) { if (!AlmostBequalUlps(xy.fX, x)) { return -1; } if (!AlmostBetweenUlps(top, xy.fY, bottom)) { return -1; } double t = (xy.fY - top) / (bottom - top); t = SkPinT(t); SkASSERT(between(0, t, 1)); double realPtY = (1 - t) * top + t * bottom; SkDVector distU = {xy.fX - x, xy.fY - realPtY}; double distSq = distU.fX * distU.fX + distU.fY * distU.fY; double dist = sqrt(distSq); // OPTIMIZATION: can we compare against distSq instead ? double tiniest = std::min(std::min(x, top), bottom); double largest = std::max(std::max(x, top), bottom); largest = std::max(largest, -tiniest); if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? return -1; } return t; } static bool findChaseOp(SkTDArray& chase, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, SkOpSegment** result) { while (!chase.empty()) { SkOpSpanBase* span = chase.back(); chase.pop_back(); // OPTIMIZE: prev makes this compatible with old code -- but is it necessary? *startPtr = span->ptT()->prev()->span(); SkOpSegment* segment = (*startPtr)->segment(); bool done = true; *endPtr = nullptr; if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done)) { *startPtr = last->start(); *endPtr = last->end(); #if TRY_ROTATE *chase.insert(0) = span; #else *chase.append() = span; #endif *result = last->segment(); return true; } if (done) { continue; } int winding; bool sortable; const SkOpAngle* angle = AngleWinding(*startPtr, *endPtr, &winding, &sortable); if (!angle) { *result = nullptr; return true; } if (winding == SK_MinS32) { continue; } int sumMiWinding, sumSuWinding; if (sortable) { segment = angle->segment(); sumMiWinding = segment->updateWindingReverse(angle); if (sumMiWinding == SK_MinS32) { SkASSERT(segment->globalState()->debugSkipAssert()); *result = nullptr; return true; } sumSuWinding = segment->updateOppWindingReverse(angle); if (sumSuWinding == SK_MinS32) { SkASSERT(segment->globalState()->debugSkipAssert()); *result = nullptr; return true; } if (segment->operand()) { using std::swap; swap(sumMiWinding, sumSuWinding); } } SkOpSegment* first = nullptr; const SkOpAngle* firstAngle = angle; while ((angle = angle->next()) != firstAngle) { segment = angle->segment(); SkOpSpanBase* start = angle->start(); SkOpSpanBase* end = angle->end(); int maxWinding = 0, sumWinding = 0, oppMaxWinding = 0, oppSumWinding = 0; if (sortable) { segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); } if (!segment->done(angle)) { if (!first && (sortable || start->starter(end)->windSum() != SK_MinS32)) { first = segment; *startPtr = start; *endPtr = end; } // OPTIMIZATION: should this also add to the chase? if (sortable) { if (!segment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding, angle, nullptr)) { return false; } } } } if (first) { #if TRY_ROTATE *chase.insert(0) = span; #else *chase.append() = span; #endif *result = first; return true; } } *result = nullptr; return true; } static bool bridgeOp(SkOpContourHead* contourList, const SkPathOp op, const int xorMask, const int xorOpMask, SkPathWriter* writer) { bool unsortable = false; bool lastSimple = false; bool simple = false; do { SkOpSpan* span = FindSortableTop(contourList); if (!span) { break; } SkOpSegment* current = span->segment(); SkOpSpanBase* start = span->next(); SkOpSpanBase* end = span; SkTDArray chase; do { if (current->activeOp(start, end, xorMask, xorOpMask, op)) { do { if (!unsortable && current->done()) { break; } SkASSERT(unsortable || !current->done()); SkOpSpanBase* nextStart = start; SkOpSpanBase* nextEnd = end; lastSimple = simple; SkOpSegment* next = current->findNextOp(&chase, &nextStart, &nextEnd, &unsortable, &simple, op, xorMask, xorOpMask); if (!next) { if (!unsortable && writer->hasMove() && current->verb() != SkPath::kLine_Verb && !writer->isClosed()) { if (!current->addCurveTo(start, end, writer)) { return false; } if (!writer->isClosed()) { SkPathOpsDebug::ShowActiveSpans(contourList); } } else if (lastSimple) { if (!current->addCurveTo(start, end, writer)) { return false; } } break; } #if DEBUG_FLOW SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, current->debugID(), start->pt().fX, start->pt().fY, end->pt().fX, end->pt().fY); #endif if (!current->addCurveTo(start, end, writer)) { return false; } current = next; start = nextStart; end = nextEnd; } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); if (current->activeWinding(start, end) && !writer->isClosed()) { SkOpSpan* spanStart = start->starter(end); if (!spanStart->done()) { if (!current->addCurveTo(start, end, writer)) { return false; } current->markDone(spanStart); } } writer->finishContour(); } else { SkOpSpanBase* last; if (!current->markAndChaseDone(start, end, &last)) { return false; } if (last && !last->chased()) { last->setChased(true); SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); *chase.append() = last; #if DEBUG_WINDING SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); if (!last->final()) { SkDebugf(" windSum=%d", last->upCast()->windSum()); } SkDebugf("\n"); #endif } } if (!findChaseOp(chase, &start, &end, ¤t)) { return false; } SkPathOpsDebug::ShowActiveSpans(contourList); if (!current) { break; } } while (true); } while (true); return true; } // diagram of why this simplifcation is possible is here: // https://skia.org/dev/present/pathops link at bottom of the page // https://drive.google.com/file/d/0BwoLUwz9PYkHLWpsaXd0UDdaN00/view?usp=sharing static const SkPathOp gOpInverse[kReverseDifference_SkPathOp + 1][2][2] = { // inside minuend outside minuend // inside subtrahend outside subtrahend inside subtrahend outside subtrahend {{ kDifference_SkPathOp, kIntersect_SkPathOp }, { kUnion_SkPathOp, kReverseDifference_SkPathOp }}, {{ kIntersect_SkPathOp, kDifference_SkPathOp }, { kReverseDifference_SkPathOp, kUnion_SkPathOp }}, {{ kUnion_SkPathOp, kReverseDifference_SkPathOp }, { kDifference_SkPathOp, kIntersect_SkPathOp }}, {{ kXOR_SkPathOp, kXOR_SkPathOp }, { kXOR_SkPathOp, kXOR_SkPathOp }}, {{ kReverseDifference_SkPathOp, kUnion_SkPathOp }, { kIntersect_SkPathOp, kDifference_SkPathOp }}, }; static const bool gOutInverse[kReverseDifference_SkPathOp + 1][2][2] = { {{ false, false }, { true, false }}, // diff {{ false, false }, { false, true }}, // sect {{ false, true }, { true, true }}, // union {{ false, true }, { true, false }}, // xor {{ false, true }, { false, false }}, // rev diff }; #if DEBUG_T_SECT_LOOP_COUNT SkOpGlobalState debugWorstState(nullptr, nullptr SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr)); void ReportPathOpsDebugging() { debugWorstState.debugLoopReport(); } extern void (*gVerboseFinalize)(); #endif bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { #if DEBUG_DUMP_VERIFY #ifndef SK_DEBUG const char* testName = "release"; #endif if (SkPathOpsDebug::gDumpOp) { DumpOp(one, two, op, testName); } #endif op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()]; bool inverseFill = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]; SkPathFillType fillType = inverseFill ? SkPathFillType::kInverseEvenOdd : SkPathFillType::kEvenOdd; SkRect rect1, rect2; if (kIntersect_SkPathOp == op && one.isRect(&rect1) && two.isRect(&rect2)) { result->reset(); result->setFillType(fillType); if (rect1.intersect(rect2)) { result->addRect(rect1); } return true; } if (one.isEmpty() || two.isEmpty()) { SkPath work; switch (op) { case kIntersect_SkPathOp: break; case kUnion_SkPathOp: case kXOR_SkPathOp: work = one.isEmpty() ? two : one; break; case kDifference_SkPathOp: if (!one.isEmpty()) { work = one; } break; case kReverseDifference_SkPathOp: if (!two.isEmpty()) { work = two; } break; default: SkASSERT(0); // unhandled case } if (inverseFill != work.isInverseFillType()) { work.toggleInverseFillType(); } return Simplify(work, result); } SkSTArenaAlloc<4096> allocator; // FIXME: add a constant expression here, tune SkOpContour contour; SkOpContourHead* contourList = static_cast(&contour); SkOpGlobalState globalState(contourList, &allocator SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); SkOpCoincidence coincidence(&globalState); const SkPath* minuend = &one; const SkPath* subtrahend = &two; if (op == kReverseDifference_SkPathOp) { using std::swap; swap(minuend, subtrahend); op = kDifference_SkPathOp; } #if DEBUG_SORT SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; #endif // turn path into list of segments SkOpEdgeBuilder builder(*minuend, contourList, &globalState); if (builder.unparseable()) { return false; } const int xorMask = builder.xorMask(); builder.addOperand(*subtrahend); if (!builder.finish()) { return false; } #if DEBUG_DUMP_SEGMENTS contourList->dumpSegments("seg", op); #endif const int xorOpMask = builder.xorMask(); if (!SortContourList(&contourList, xorMask == kEvenOdd_PathOpsMask, xorOpMask == kEvenOdd_PathOpsMask)) { result->reset(); result->setFillType(fillType); return true; } // find all intersections between segments SkOpContour* current = contourList; do { SkOpContour* next = current; while (AddIntersectTs(current, next, &coincidence) && (next = next->next())) ; } while ((current = current->next())); #if DEBUG_VALIDATE globalState.setPhase(SkOpPhase::kWalking); #endif bool success = HandleCoincidence(contourList, &coincidence); #if DEBUG_COIN globalState.debugAddToGlobalCoinDicts(); #endif if (!success) { return false; } #if DEBUG_ALIGNMENT contourList->dumpSegments("aligned"); #endif // construct closed contours SkPath original = *result; result->reset(); result->setFillType(fillType); SkPathWriter wrapper(*result); if (!bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper)) { *result = original; return false; } wrapper.assemble(); // if some edges could not be resolved, assemble remaining #if DEBUG_T_SECT_LOOP_COUNT static SkMutex& debugWorstLoop = *(new SkMutex); { SkAutoMutexExclusive autoM(debugWorstLoop); if (!gVerboseFinalize) { gVerboseFinalize = &ReportPathOpsDebugging; } debugWorstState.debugDoYourWorst(&globalState); } #endif return true; } bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { #if DEBUG_DUMP_VERIFY if (SkPathOpsDebug::gVerifyOp) { if (!OpDebug(one, two, op, result SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr))) { ReportOpFail(one, two, op); return false; } VerifyOp(one, two, op, *result); return true; } #endif return OpDebug(one, two, op, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); } // from blackpawn.com/texts/pointinpoly static bool pointInTriangle(const SkDPoint fPts[3], const SkDPoint& test) { SkDVector v0 = fPts[2] - fPts[0]; SkDVector v1 = fPts[1] - fPts[0]; SkDVector v2 = test - fPts[0]; double dot00 = v0.dot(v0); double dot01 = v0.dot(v1); double dot02 = v0.dot(v2); double dot11 = v1.dot(v1); double dot12 = v1.dot(v2); // Compute barycentric coordinates double denom = dot00 * dot11 - dot01 * dot01; double u = dot11 * dot02 - dot01 * dot12; double v = dot00 * dot12 - dot01 * dot02; // Check if point is in triangle if (denom >= 0) { return u >= 0 && v >= 0 && u + v < denom; } return u <= 0 && v <= 0 && u + v > denom; } static bool matchesEnd(const SkDPoint fPts[3], const SkDPoint& test) { return fPts[0] == test || fPts[2] == test; } /* started with at_most_end_pts_in_common from SkDQuadIntersection.cpp */ // Do a quick reject by rotating all points relative to a line formed by // a pair of one quad's points. If the 2nd quad's points // are on the line or on the opposite side from the 1st quad's 'odd man', the // curves at most intersect at the endpoints. /* if returning true, check contains true if quad's hull collapsed, making the cubic linear if returning false, check contains true if the the quad pair have only the end point in common */ bool SkDQuad::hullIntersects(const SkDQuad& q2, bool* isLinear) const { bool linear = true; for (int oddMan = 0; oddMan < kPointCount; ++oddMan) { const SkDPoint* endPt[2]; this->otherPts(oddMan, endPt); double origX = endPt[0]->fX; double origY = endPt[0]->fY; double adj = endPt[1]->fX - origX; double opp = endPt[1]->fY - origY; double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; if (approximately_zero(sign)) { continue; } linear = false; bool foundOutlier = false; for (int n = 0; n < kPointCount; ++n) { double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; if (test * sign > 0 && !precisely_zero(test)) { foundOutlier = true; break; } } if (!foundOutlier) { return false; } } if (linear && !matchesEnd(fPts, q2.fPts[0]) && !matchesEnd(fPts, q2.fPts[2])) { // if the end point of the opposite quad is inside the hull that is nearly a line, // then representing the quad as a line may cause the intersection to be missed. // Check to see if the endpoint is in the triangle. if (pointInTriangle(fPts, q2.fPts[0]) || pointInTriangle(fPts, q2.fPts[2])) { linear = false; } } *isLinear = linear; return true; } bool SkDQuad::hullIntersects(const SkDConic& conic, bool* isLinear) const { return conic.hullIntersects(*this, isLinear); } bool SkDQuad::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { return cubic.hullIntersects(*this, isLinear); } /* bit twiddling for finding the off curve index (x&~m is the pair in [0,1,2] excluding oddMan) oddMan opp x=oddMan^opp x=x-oddMan m=x>>2 x&~m 0 1 1 1 0 1 2 2 2 0 2 1 1 0 -1 -1 0 2 3 2 0 2 2 1 3 1 0 1 2 0 -2 -1 0 */ void SkDQuad::otherPts(int oddMan, const SkDPoint* endPt[2]) const { for (int opp = 1; opp < kPointCount; ++opp) { int end = (oddMan ^ opp) - oddMan; // choose a value not equal to oddMan end &= ~(end >> 2); // if the value went negative, set it to zero endPt[opp - 1] = &fPts[end]; } } int SkDQuad::AddValidTs(double s[], int realRoots, double* t) { int foundRoots = 0; for (int index = 0; index < realRoots; ++index) { double tValue = s[index]; if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) { if (approximately_less_than_zero(tValue)) { tValue = 0; } else if (approximately_greater_than_one(tValue)) { tValue = 1; } for (int idx2 = 0; idx2 < foundRoots; ++idx2) { if (approximately_equal(t[idx2], tValue)) { goto nextRoot; } } t[foundRoots++] = tValue; } nextRoot: {} } return foundRoots; } // note: caller expects multiple results to be sorted smaller first // note: http://en.wikipedia.org/wiki/Loss_of_significance has an interesting // analysis of the quadratic equation, suggesting why the following looks at // the sign of B -- and further suggesting that the greatest loss of precision // is in b squared less two a c int SkDQuad::RootsValidT(double A, double B, double C, double t[2]) { double s[2]; int realRoots = RootsReal(A, B, C, s); int foundRoots = AddValidTs(s, realRoots, t); return foundRoots; } static int handle_zero(const double B, const double C, double s[2]) { if (approximately_zero(B)) { s[0] = 0; return C == 0; } s[0] = -C / B; return 1; } /* Numeric Solutions (5.6) suggests to solve the quadratic by computing Q = -1/2(B + sgn(B)Sqrt(B^2 - 4 A C)) and using the roots t1 = Q / A t2 = C / Q */ // this does not discard real roots <= 0 or >= 1 // TODO(skbug.com/14063) Deduplicate with SkQuads::RootsReal int SkDQuad::RootsReal(const double A, const double B, const double C, double s[2]) { if (!A) { return handle_zero(B, C, s); } const double p = B / (2 * A); const double q = C / A; if (approximately_zero(A) && (approximately_zero_inverse(p) || approximately_zero_inverse(q))) { return handle_zero(B, C, s); } /* normal form: x^2 + px + q = 0 */ const double p2 = p * p; if (!AlmostDequalUlps(p2, q) && p2 < q) { return 0; } double sqrt_D = 0; if (p2 > q) { sqrt_D = sqrt(p2 - q); } s[0] = sqrt_D - p; s[1] = -sqrt_D - p; return 1 + !AlmostDequalUlps(s[0], s[1]); } bool SkDQuad::isLinear(int startIndex, int endIndex) const { SkLineParameters lineParameters; lineParameters.quadEndPoints(*this, startIndex, endIndex); // FIXME: maybe it's possible to avoid this and compare non-normalized lineParameters.normalize(); double distance = lineParameters.controlPtDistance(*this); double tiniest = std::min(std::min(std::min(std::min(std::min(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); double largest = std::max(std::max(std::max(std::max(std::max(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); largest = std::max(largest, -tiniest); return approximately_zero_when_compared_to(distance, largest); } SkDVector SkDQuad::dxdyAtT(double t) const { double a = t - 1; double b = 1 - 2 * t; double c = t; SkDVector result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; if (result.fX == 0 && result.fY == 0) { if (zero_or_one(t)) { result = fPts[2] - fPts[0]; } else { // incomplete SkDebugf("!q"); } } return result; } // OPTIMIZE: assert if caller passes in t == 0 / t == 1 ? SkDPoint SkDQuad::ptAtT(double t) const { if (0 == t) { return fPts[0]; } if (1 == t) { return fPts[2]; } double one_t = 1 - t; double a = one_t * one_t; double b = 2 * one_t * t; double c = t * t; SkDPoint result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; return result; } static double interp_quad_coords(const double* src, double t) { if (0 == t) { return src[0]; } if (1 == t) { return src[4]; } double ab = SkDInterp(src[0], src[2], t); double bc = SkDInterp(src[2], src[4], t); double abc = SkDInterp(ab, bc, t); return abc; } bool SkDQuad::monotonicInX() const { return between(fPts[0].fX, fPts[1].fX, fPts[2].fX); } bool SkDQuad::monotonicInY() const { return between(fPts[0].fY, fPts[1].fY, fPts[2].fY); } /* Given a quadratic q, t1, and t2, find a small quadratic segment. The new quadratic is defined by A, B, and C, where A = c[0]*(1 - t1)*(1 - t1) + 2*c[1]*t1*(1 - t1) + c[2]*t1*t1 C = c[3]*(1 - t1)*(1 - t1) + 2*c[2]*t1*(1 - t1) + c[1]*t1*t1 To find B, compute the point halfway between t1 and t2: q(at (t1 + t2)/2) == D Next, compute where D must be if we know the value of B: _12 = A/2 + B/2 12_ = B/2 + C/2 123 = A/4 + B/2 + C/4 = D Group the known values on one side: B = D*2 - A/2 - C/2 */ // OPTIMIZE? : special case t1 = 1 && t2 = 0 SkDQuad SkDQuad::subDivide(double t1, double t2) const { if (0 == t1 && 1 == t2) { return *this; } SkDQuad dst; double ax = dst[0].fX = interp_quad_coords(&fPts[0].fX, t1); double ay = dst[0].fY = interp_quad_coords(&fPts[0].fY, t1); double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2); double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2); double cx = dst[2].fX = interp_quad_coords(&fPts[0].fX, t2); double cy = dst[2].fY = interp_quad_coords(&fPts[0].fY, t2); /* bx = */ dst[1].fX = 2 * dx - (ax + cx) / 2; /* by = */ dst[1].fY = 2 * dy - (ay + cy) / 2; return dst; } void SkDQuad::align(int endIndex, SkDPoint* dstPt) const { if (fPts[endIndex].fX == fPts[1].fX) { dstPt->fX = fPts[endIndex].fX; } if (fPts[endIndex].fY == fPts[1].fY) { dstPt->fY = fPts[endIndex].fY; } } SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const { SkASSERT(t1 != t2); SkDPoint b; SkDQuad sub = subDivide(t1, t2); SkDLine b0 = {{a, sub[1] + (a - sub[0])}}; SkDLine b1 = {{c, sub[1] + (c - sub[2])}}; SkIntersections i; i.intersectRay(b0, b1); if (i.used() == 1 && i[0][0] >= 0 && i[1][0] >= 0) { b = i.pt(0); } else { SkASSERT(i.used() <= 2); return SkDPoint::Mid(b0[1], b1[1]); } if (t1 == 0 || t2 == 0) { align(0, &b); } if (t1 == 1 || t2 == 1) { align(2, &b); } if (AlmostBequalUlps(b.fX, a.fX)) { b.fX = a.fX; } else if (AlmostBequalUlps(b.fX, c.fX)) { b.fX = c.fX; } if (AlmostBequalUlps(b.fY, a.fY)) { b.fY = a.fY; } else if (AlmostBequalUlps(b.fY, c.fY)) { b.fY = c.fY; } return b; } /* classic one t subdivision */ static void interp_quad_coords(const double* src, double* dst, double t) { double ab = SkDInterp(src[0], src[2], t); double bc = SkDInterp(src[2], src[4], t); dst[0] = src[0]; dst[2] = ab; dst[4] = SkDInterp(ab, bc, t); dst[6] = bc; dst[8] = src[4]; } SkDQuadPair SkDQuad::chopAt(double t) const { SkDQuadPair dst; interp_quad_coords(&fPts[0].fX, &dst.pts[0].fX, t); interp_quad_coords(&fPts[0].fY, &dst.pts[0].fY, t); return dst; } static int valid_unit_divide(double numer, double denom, double* ratio) { if (numer < 0) { numer = -numer; denom = -denom; } if (denom == 0 || numer == 0 || numer >= denom) { return 0; } double r = numer / denom; if (r == 0) { // catch underflow if numer <<<< denom return 0; } *ratio = r; return 1; } /** Quad'(t) = At + B, where A = 2(a - 2b + c) B = 2(b - a) Solve for t, only if it fits between 0 < t < 1 */ int SkDQuad::FindExtrema(const double src[], double tValue[1]) { /* At + B == 0 t = -B / A */ double a = src[0]; double b = src[2]; double c = src[4]; return valid_unit_divide(a - b, a - b - b + c, tValue); } /* Parameterization form, given A*t*t + 2*B*t*(1-t) + C*(1-t)*(1-t) * * a = A - 2*B + C * b = 2*B - 2*C * c = C */ void SkDQuad::SetABC(const double* quad, double* a, double* b, double* c) { *a = quad[0]; // a = A *b = 2 * quad[2]; // b = 2*B *c = quad[4]; // c = C *b -= *c; // b = 2*B - C *a -= *b; // a = A - 2*B + C *b -= *c; // b = 2*B - 2*C } int SkTQuad::intersectRay(SkIntersections* i, const SkDLine& line) const { return i->intersectRay(fQuad, line); } bool SkTQuad::hullIntersects(const SkDConic& conic, bool* isLinear) const { return conic.hullIntersects(fQuad, isLinear); } bool SkTQuad::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { return cubic.hullIntersects(fQuad, isLinear); } void SkTQuad::setBounds(SkDRect* rect) const { rect->setBounds(fQuad); } void SkDRect::setBounds(const SkDQuad& curve, const SkDQuad& sub, double startT, double endT) { set(sub[0]); add(sub[2]); double tValues[2]; int roots = 0; if (!sub.monotonicInX()) { roots = SkDQuad::FindExtrema(&sub[0].fX, tValues); } if (!sub.monotonicInY()) { roots += SkDQuad::FindExtrema(&sub[0].fY, &tValues[roots]); } for (int index = 0; index < roots; ++index) { double t = startT + (endT - startT) * tValues[index]; add(curve.ptAtT(t)); } } void SkDRect::setBounds(const SkDConic& curve, const SkDConic& sub, double startT, double endT) { set(sub[0]); add(sub[2]); double tValues[2]; int roots = 0; if (!sub.monotonicInX()) { roots = SkDConic::FindExtrema(&sub[0].fX, sub.fWeight, tValues); } if (!sub.monotonicInY()) { roots += SkDConic::FindExtrema(&sub[0].fY, sub.fWeight, &tValues[roots]); } for (int index = 0; index < roots; ++index) { double t = startT + (endT - startT) * tValues[index]; add(curve.ptAtT(t)); } } void SkDRect::setBounds(const SkDCubic& curve, const SkDCubic& sub, double startT, double endT) { set(sub[0]); add(sub[3]); double tValues[4]; int roots = 0; if (!sub.monotonicInX()) { roots = SkDCubic::FindExtrema(&sub[0].fX, tValues); } if (!sub.monotonicInY()) { roots += SkDCubic::FindExtrema(&sub[0].fY, &tValues[roots]); } for (int index = 0; index < roots; ++index) { double t = startT + (endT - startT) * tValues[index]; add(curve.ptAtT(t)); } } void SkDRect::setBounds(const SkTCurve& curve) { curve.setBounds(this); } static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* writer) { bool unsortable = false; do { SkOpSpan* span = FindSortableTop(contourList); if (!span) { break; } SkOpSegment* current = span->segment(); SkOpSpanBase* start = span->next(); SkOpSpanBase* end = span; SkTDArray chase; do { if (current->activeWinding(start, end)) { do { if (!unsortable && current->done()) { break; } SkASSERT(unsortable || !current->done()); SkOpSpanBase* nextStart = start; SkOpSpanBase* nextEnd = end; SkOpSegment* next = current->findNextWinding(&chase, &nextStart, &nextEnd, &unsortable); if (!next) { break; } #if DEBUG_FLOW SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, current->debugID(), start->pt().fX, start->pt().fY, end->pt().fX, end->pt().fY); #endif if (!current->addCurveTo(start, end, writer)) { return false; } current = next; start = nextStart; end = nextEnd; } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); if (current->activeWinding(start, end) && !writer->isClosed()) { SkOpSpan* spanStart = start->starter(end); if (!spanStart->done()) { if (!current->addCurveTo(start, end, writer)) { return false; } current->markDone(spanStart); } } writer->finishContour(); } else { SkOpSpanBase* last; if (!current->markAndChaseDone(start, end, &last)) { return false; } if (last && !last->chased()) { last->setChased(true); SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); *chase.append() = last; #if DEBUG_WINDING SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); if (!last->final()) { SkDebugf(" windSum=%d", last->upCast()->windSum()); } SkDebugf("\n"); #endif } } current = FindChase(&chase, &start, &end); SkPathOpsDebug::ShowActiveSpans(contourList); if (!current) { break; } } while (true); } while (true); return true; } // returns true if all edges were processed static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* writer) { bool unsortable = false; int safetyNet = 1000000; do { SkOpSpan* span = FindUndone(contourList); if (!span) { break; } SkOpSegment* current = span->segment(); SkOpSpanBase* start = span->next(); SkOpSpanBase* end = span; do { if (--safetyNet < 0) { return false; } if (!unsortable && current->done()) { break; } SkASSERT(unsortable || !current->done()); SkOpSpanBase* nextStart = start; SkOpSpanBase* nextEnd = end; SkOpSegment* next = current->findNextXor(&nextStart, &nextEnd, &unsortable); if (!next) { break; } #if DEBUG_FLOW SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, current->debugID(), start->pt().fX, start->pt().fY, end->pt().fX, end->pt().fY); #endif if (!current->addCurveTo(start, end, writer)) { return false; } current = next; start = nextStart; end = nextEnd; } while (!writer->isClosed() && (!unsortable || !start->starter(end)->done())); if (!writer->isClosed()) { SkOpSpan* spanStart = start->starter(end); if (!spanStart->done()) { return false; } } writer->finishContour(); SkPathOpsDebug::ShowActiveSpans(contourList); } while (true); return true; } static bool path_is_trivial(const SkPath& path) { SkPath::Iter iter(path, true); SkPath::Verb verb; SkPoint points[4]; class Trivializer { SkPoint prevPt{0,0}; SkVector prevVec{0,0}; public: void moveTo(const SkPoint& currPt) { prevPt = currPt; prevVec = {0, 0}; } bool addTrivialContourPoint(const SkPoint& currPt) { if (currPt == prevPt) { return true; } // There are more numericaly stable ways of determining if many points are co-linear. // However, this mirrors SkPath's Convexicator for consistency. SkVector currVec = currPt - prevPt; if (SkPoint::CrossProduct(prevVec, currVec) != 0) { return false; } prevVec = currVec; prevPt = currPt; return true; } } triv; while ((verb = iter.next(points)) != SkPath::kDone_Verb) { switch (verb) { case SkPath::kMove_Verb: triv.moveTo(points[0]); break; case SkPath::kCubic_Verb: if (!triv.addTrivialContourPoint(points[3])) { return false; } [[fallthrough]]; case SkPath::kConic_Verb: case SkPath::kQuad_Verb: if (!triv.addTrivialContourPoint(points[2])) { return false; } [[fallthrough]]; case SkPath::kLine_Verb: if (!triv.addTrivialContourPoint(points[1])) { return false; } if (!triv.addTrivialContourPoint(points[0])) { return false; } break; case SkPath::kClose_Verb: case SkPath::kDone_Verb: break; } } return true; } // FIXME : add this as a member of SkPath bool SimplifyDebug(const SkPath& path, SkPath* result SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { // returns 1 for evenodd, -1 for winding, regardless of inverse-ness SkPathFillType fillType = path.isInverseFillType() ? SkPathFillType::kInverseEvenOdd : SkPathFillType::kEvenOdd; if (path.isConvex()) { // If the path is trivially convex, simplify to empty. if (path_is_trivial(path)) { result->reset(); } else if (result != &path) { *result = path; } result->setFillType(fillType); return true; } // turn path into list of segments SkSTArenaAlloc<4096> allocator; // FIXME: constant-ize, tune SkOpContour contour; SkOpContourHead* contourList = static_cast(&contour); SkOpGlobalState globalState(contourList, &allocator SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); SkOpCoincidence coincidence(&globalState); #if DEBUG_DUMP_VERIFY #ifndef SK_DEBUG const char* testName = "release"; #endif if (SkPathOpsDebug::gDumpOp) { DumpSimplify(path, testName); } #endif #if DEBUG_SORT SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; #endif SkOpEdgeBuilder builder(path, contourList, &globalState); if (!builder.finish()) { return false; } #if DEBUG_DUMP_SEGMENTS contour.dumpSegments(); #endif if (!SortContourList(&contourList, false, false)) { result->reset(); result->setFillType(fillType); return true; } // find all intersections between segments SkOpContour* current = contourList; do { SkOpContour* next = current; while (AddIntersectTs(current, next, &coincidence) && (next = next->next())); } while ((current = current->next())); #if DEBUG_VALIDATE globalState.setPhase(SkOpPhase::kWalking); #endif bool success = HandleCoincidence(contourList, &coincidence); #if DEBUG_COIN globalState.debugAddToGlobalCoinDicts(); #endif if (!success) { return false; } #if DEBUG_DUMP_ALIGNMENT contour.dumpSegments("aligned"); #endif // construct closed contours result->reset(); result->setFillType(fillType); SkPathWriter wrapper(*result); if (builder.xorMask() == kWinding_PathOpsMask ? !bridgeWinding(contourList, &wrapper) : !bridgeXor(contourList, &wrapper)) { return false; } wrapper.assemble(); // if some edges could not be resolved, assemble remaining return true; } bool Simplify(const SkPath& path, SkPath* result) { #if DEBUG_DUMP_VERIFY if (SkPathOpsDebug::gVerifyOp) { if (!SimplifyDebug(path, result SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr))) { ReportSimplifyFail(path); return false; } VerifySimplify(path, *result); return true; } #endif return SimplifyDebug(path, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); } using namespace skia_private; #define COINCIDENT_SPAN_COUNT 9 void SkTCoincident::setPerp(const SkTCurve& c1, double t, const SkDPoint& cPt, const SkTCurve& c2) { SkDVector dxdy = c1.dxdyAtT(t); SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; SkIntersections i SkDEBUGCODE((c1.globalState())); int used = i.intersectRay(c2, perp); // only keep closest if (used == 0 || used == 3) { this->init(); return; } fPerpT = i[0][0]; fPerpPt = i.pt(0); SkASSERT(used <= 2); if (used == 2) { double distSq = (fPerpPt - cPt).lengthSquared(); double dist2Sq = (i.pt(1) - cPt).lengthSquared(); if (dist2Sq < distSq) { fPerpT = i[0][1]; fPerpPt = i.pt(1); } } #if DEBUG_T_SECT SkDebugf("setPerp t=%1.9g cPt=(%1.9g,%1.9g) %s oppT=%1.9g fPerpPt=(%1.9g,%1.9g)\n", t, cPt.fX, cPt.fY, cPt.approximatelyEqual(fPerpPt) ? "==" : "!=", fPerpT, fPerpPt.fX, fPerpPt.fY); #endif fMatch = cPt.approximatelyEqual(fPerpPt); #if DEBUG_T_SECT if (fMatch) { SkDebugf("%s", ""); // allow setting breakpoint } #endif } void SkTSpan::addBounded(SkTSpan* span, SkArenaAlloc* heap) { SkTSpanBounded* bounded = heap->make(); bounded->fBounded = span; bounded->fNext = fBounded; fBounded = bounded; } SkTSpan* SkTSect::addFollowing( SkTSpan* prior) { SkTSpan* result = this->addOne(); SkDEBUGCODE(result->debugSetGlobalState(this->globalState())); result->fStartT = prior ? prior->fEndT : 0; SkTSpan* next = prior ? prior->fNext : fHead; result->fEndT = next ? next->fStartT : 1; result->fPrev = prior; result->fNext = next; if (prior) { prior->fNext = result; } else { fHead = result; } if (next) { next->fPrev = result; } result->resetBounds(fCurve); // world may not be consistent to call validate here result->validate(); return result; } void SkTSect::addForPerp(SkTSpan* span, double t) { if (!span->hasOppT(t)) { SkTSpan* priorSpan; SkTSpan* opp = this->spanAtT(t, &priorSpan); if (!opp) { opp = this->addFollowing(priorSpan); #if DEBUG_PERP SkDebugf("%s priorSpan=%d t=%1.9g opp=%d\n", __FUNCTION__, priorSpan ? priorSpan->debugID() : -1, t, opp->debugID()); #endif } #if DEBUG_PERP opp->dump(); SkDebugf("\n"); SkDebugf("%s addBounded span=%d opp=%d\n", __FUNCTION__, priorSpan ? priorSpan->debugID() : -1, opp->debugID()); #endif opp->addBounded(span, &fHeap); span->addBounded(opp, &fHeap); } this->validate(); #if DEBUG_T_SECT span->validatePerpT(t); #endif } double SkTSpan::closestBoundedT(const SkDPoint& pt) const { double result = -1; double closest = DBL_MAX; const SkTSpanBounded* testBounded = fBounded; while (testBounded) { const SkTSpan* test = testBounded->fBounded; double startDist = test->pointFirst().distanceSquared(pt); if (closest > startDist) { closest = startDist; result = test->fStartT; } double endDist = test->pointLast().distanceSquared(pt); if (closest > endDist) { closest = endDist; result = test->fEndT; } testBounded = testBounded->fNext; } SkASSERT(between(0, result, 1)); return result; } #ifdef SK_DEBUG bool SkTSpan::debugIsBefore(const SkTSpan* span) const { const SkTSpan* work = this; do { if (span == work) { return true; } } while ((work = work->fNext)); return false; } #endif bool SkTSpan::contains(double t) const { const SkTSpan* work = this; do { if (between(work->fStartT, t, work->fEndT)) { return true; } } while ((work = work->fNext)); return false; } const SkTSect* SkTSpan::debugOpp() const { return SkDEBUGRELEASE(fDebugSect->debugOpp(), nullptr); } SkTSpan* SkTSpan::findOppSpan( const SkTSpan* opp) const { SkTSpanBounded* bounded = fBounded; while (bounded) { SkTSpan* test = bounded->fBounded; if (opp == test) { return test; } bounded = bounded->fNext; } return nullptr; } // returns 0 if no hull intersection // 1 if hulls intersect // 2 if hulls only share a common endpoint // -1 if linear and further checking is required int SkTSpan::hullCheck(const SkTSpan* opp, bool* start, bool* oppStart) { if (fIsLinear) { return -1; } bool ptsInCommon; if (onlyEndPointsInCommon(opp, start, oppStart, &ptsInCommon)) { SkASSERT(ptsInCommon); return 2; } bool linear; if (fPart->hullIntersects(*opp->fPart, &linear)) { if (!linear) { // check set true if linear return 1; } fIsLinear = true; fIsLine = fPart->controlsInside(); return ptsInCommon ? 1 : -1; } // hull is not linear; check set true if intersected at the end points return ((int) ptsInCommon) << 1; // 0 or 2 } // OPTIMIZE ? If at_most_end_pts_in_common detects that one quad is near linear, // use line intersection to guess a better split than 0.5 // OPTIMIZE Once at_most_end_pts_in_common detects linear, mark span so all future splits are linear int SkTSpan::hullsIntersect(SkTSpan* opp, bool* start, bool* oppStart) { if (!fBounds.intersects(opp->fBounds)) { return 0; } int hullSect = this->hullCheck(opp, start, oppStart); if (hullSect >= 0) { return hullSect; } hullSect = opp->hullCheck(this, oppStart, start); if (hullSect >= 0) { return hullSect; } return -1; } void SkTSpan::init(const SkTCurve& c) { fPrev = fNext = nullptr; fStartT = 0; fEndT = 1; fBounded = nullptr; resetBounds(c); } bool SkTSpan::initBounds(const SkTCurve& c) { if (SkDoubleIsNaN(fStartT) || SkDoubleIsNaN(fEndT)) { return false; } c.subDivide(fStartT, fEndT, fPart); fBounds.setBounds(*fPart); fCoinStart.init(); fCoinEnd.init(); fBoundsMax = std::max(fBounds.width(), fBounds.height()); fCollapsed = fPart->collapsed(); fHasPerp = false; fDeleted = false; #if DEBUG_T_SECT if (fCollapsed) { SkDebugf("%s", ""); // for convenient breakpoints } #endif return fBounds.valid(); } bool SkTSpan::linearsIntersect(SkTSpan* span) { int result = this->linearIntersects(*span->fPart); if (result <= 1) { return SkToBool(result); } SkASSERT(span->fIsLinear); result = span->linearIntersects(*fPart); // SkASSERT(result <= 1); return SkToBool(result); } double SkTSpan::linearT(const SkDPoint& pt) const { SkDVector len = this->pointLast() - this->pointFirst(); return fabs(len.fX) > fabs(len.fY) ? (pt.fX - this->pointFirst().fX) / len.fX : (pt.fY - this->pointFirst().fY) / len.fY; } int SkTSpan::linearIntersects(const SkTCurve& q2) const { // looks like q1 is near-linear int start = 0, end = fPart->pointLast(); // the outside points are usually the extremes if (!fPart->controlsInside()) { double dist = 0; // if there's any question, compute distance to find best outsiders for (int outer = 0; outer < this->pointCount() - 1; ++outer) { for (int inner = outer + 1; inner < this->pointCount(); ++inner) { double test = ((*fPart)[outer] - (*fPart)[inner]).lengthSquared(); if (dist > test) { continue; } dist = test; start = outer; end = inner; } } } // see if q2 is on one side of the line formed by the extreme points double origX = (*fPart)[start].fX; double origY = (*fPart)[start].fY; double adj = (*fPart)[end].fX - origX; double opp = (*fPart)[end].fY - origY; double maxPart = std::max(fabs(adj), fabs(opp)); double sign = 0; // initialization to shut up warning in release build for (int n = 0; n < q2.pointCount(); ++n) { double dx = q2[n].fY - origY; double dy = q2[n].fX - origX; double maxVal = std::max(maxPart, std::max(fabs(dx), fabs(dy))); double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; if (precisely_zero_when_compared_to(test, maxVal)) { return 1; } if (approximately_zero_when_compared_to(test, maxVal)) { return 3; } if (n == 0) { sign = test; continue; } if (test * sign < 0) { return 1; } } return 0; } bool SkTSpan::onlyEndPointsInCommon(const SkTSpan* opp, bool* start, bool* oppStart, bool* ptsInCommon) { if (opp->pointFirst() == this->pointFirst()) { *start = *oppStart = true; } else if (opp->pointFirst() == this->pointLast()) { *start = false; *oppStart = true; } else if (opp->pointLast() == this->pointFirst()) { *start = true; *oppStart = false; } else if (opp->pointLast() == this->pointLast()) { *start = *oppStart = false; } else { *ptsInCommon = false; return false; } *ptsInCommon = true; const SkDPoint* otherPts[4], * oppOtherPts[4]; // const SkDPoint* otherPts[this->pointCount() - 1], * oppOtherPts[opp->pointCount() - 1]; int baseIndex = *start ? 0 : fPart->pointLast(); fPart->otherPts(baseIndex, otherPts); opp->fPart->otherPts(*oppStart ? 0 : opp->fPart->pointLast(), oppOtherPts); const SkDPoint& base = (*fPart)[baseIndex]; for (int o1 = 0; o1 < this->pointCount() - 1; ++o1) { SkDVector v1 = *otherPts[o1] - base; for (int o2 = 0; o2 < opp->pointCount() - 1; ++o2) { SkDVector v2 = *oppOtherPts[o2] - base; if (v2.dot(v1) >= 0) { return false; } } } return true; } SkTSpan* SkTSpan::oppT(double t) const { SkTSpanBounded* bounded = fBounded; while (bounded) { SkTSpan* test = bounded->fBounded; if (between(test->fStartT, t, test->fEndT)) { return test; } bounded = bounded->fNext; } return nullptr; } bool SkTSpan::removeAllBounded() { bool deleteSpan = false; SkTSpanBounded* bounded = fBounded; while (bounded) { SkTSpan* opp = bounded->fBounded; deleteSpan |= opp->removeBounded(this); bounded = bounded->fNext; } return deleteSpan; } bool SkTSpan::removeBounded(const SkTSpan* opp) { if (fHasPerp) { bool foundStart = false; bool foundEnd = false; SkTSpanBounded* bounded = fBounded; while (bounded) { SkTSpan* test = bounded->fBounded; if (opp != test) { foundStart |= between(test->fStartT, fCoinStart.perpT(), test->fEndT); foundEnd |= between(test->fStartT, fCoinEnd.perpT(), test->fEndT); } bounded = bounded->fNext; } if (!foundStart || !foundEnd) { fHasPerp = false; fCoinStart.init(); fCoinEnd.init(); } } SkTSpanBounded* bounded = fBounded; SkTSpanBounded* prev = nullptr; while (bounded) { SkTSpanBounded* boundedNext = bounded->fNext; if (opp == bounded->fBounded) { if (prev) { prev->fNext = boundedNext; return false; } else { fBounded = boundedNext; return fBounded == nullptr; } } prev = bounded; bounded = boundedNext; } SkOPASSERT(0); return false; } bool SkTSpan::splitAt(SkTSpan* work, double t, SkArenaAlloc* heap) { fStartT = t; fEndT = work->fEndT; if (fStartT == fEndT) { fCollapsed = true; return false; } work->fEndT = t; if (work->fStartT == work->fEndT) { work->fCollapsed = true; return false; } fPrev = work; fNext = work->fNext; fIsLinear = work->fIsLinear; fIsLine = work->fIsLine; work->fNext = this; if (fNext) { fNext->fPrev = this; } this->validate(); SkTSpanBounded* bounded = work->fBounded; fBounded = nullptr; while (bounded) { this->addBounded(bounded->fBounded, heap); bounded = bounded->fNext; } bounded = fBounded; while (bounded) { bounded->fBounded->addBounded(this, heap); bounded = bounded->fNext; } return true; } void SkTSpan::validate() const { #if DEBUG_VALIDATE SkASSERT(this != fPrev); SkASSERT(this != fNext); SkASSERT(fNext == nullptr || fNext != fPrev); SkASSERT(fNext == nullptr || this == fNext->fPrev); SkASSERT(fPrev == nullptr || this == fPrev->fNext); this->validateBounded(); #endif #if DEBUG_T_SECT SkASSERT(fBounds.width() || fBounds.height() || fCollapsed); SkASSERT(fBoundsMax == std::max(fBounds.width(), fBounds.height()) || fCollapsed == 0xFF); SkASSERT(0 <= fStartT); SkASSERT(fEndT <= 1); SkASSERT(fStartT <= fEndT); SkASSERT(fBounded || fCollapsed == 0xFF); if (fHasPerp) { if (fCoinStart.isMatch()) { validatePerpT(fCoinStart.perpT()); validatePerpPt(fCoinStart.perpT(), fCoinStart.perpPt()); } if (fCoinEnd.isMatch()) { validatePerpT(fCoinEnd.perpT()); validatePerpPt(fCoinEnd.perpT(), fCoinEnd.perpPt()); } } #endif } void SkTSpan::validateBounded() const { #if DEBUG_VALIDATE const SkTSpanBounded* testBounded = fBounded; while (testBounded) { SkDEBUGCODE(const SkTSpan* overlap = testBounded->fBounded); SkASSERT(!overlap->fDeleted); #if DEBUG_T_SECT SkASSERT(((this->debugID() ^ overlap->debugID()) & 1) == 1); SkASSERT(overlap->findOppSpan(this)); #endif testBounded = testBounded->fNext; } #endif } void SkTSpan::validatePerpT(double oppT) const { const SkTSpanBounded* testBounded = fBounded; while (testBounded) { const SkTSpan* overlap = testBounded->fBounded; if (precisely_between(overlap->fStartT, oppT, overlap->fEndT)) { return; } testBounded = testBounded->fNext; } SkASSERT(0); } void SkTSpan::validatePerpPt(double t, const SkDPoint& pt) const { SkASSERT(fDebugSect->fOppSect->fCurve.ptAtT(t) == pt); } SkTSect::SkTSect(const SkTCurve& c SkDEBUGPARAMS(SkOpGlobalState* debugGlobalState) PATH_OPS_DEBUG_T_SECT_PARAMS(int id)) : fCurve(c) , fHeap(sizeof(SkTSpan) * 4) , fCoincident(nullptr) , fDeleted(nullptr) , fActiveCount(0) , fHung(false) SkDEBUGPARAMS(fDebugGlobalState(debugGlobalState)) PATH_OPS_DEBUG_T_SECT_PARAMS(fID(id)) PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugCount(0)) PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugAllocatedCount(0)) { this->resetRemovedEnds(); fHead = this->addOne(); SkDEBUGCODE(fHead->debugSetGlobalState(debugGlobalState)); fHead->init(c); } SkTSpan* SkTSect::addOne() { SkTSpan* result; if (fDeleted) { result = fDeleted; fDeleted = result->fNext; } else { result = fHeap.make(fCurve, fHeap); #if DEBUG_T_SECT ++fDebugAllocatedCount; #endif } result->reset(); result->fHasPerp = false; result->fDeleted = false; ++fActiveCount; PATH_OPS_DEBUG_T_SECT_CODE(result->fID = fDebugCount++ * 2 + fID); SkDEBUGCODE(result->fDebugSect = this); #ifdef SK_DEBUG result->debugInit(fCurve, fHeap); result->fCoinStart.debugInit(); result->fCoinEnd.debugInit(); result->fPrev = result->fNext = nullptr; result->fBounds.debugInit(); result->fStartT = result->fEndT = result->fBoundsMax = SK_ScalarNaN; result->fCollapsed = result->fIsLinear = result->fIsLine = 0xFF; #endif return result; } bool SkTSect::binarySearchCoin(SkTSect* sect2, double tStart, double tStep, double* resultT, double* oppT, SkTSpan** oppFirst) { SkTSpan work(fCurve, fHeap); double result = work.fStartT = work.fEndT = tStart; SkDEBUGCODE(work.fDebugSect = this); SkDPoint last = fCurve.ptAtT(tStart); SkDPoint oppPt; bool flip = false; bool contained = false; bool down = tStep < 0; const SkTCurve& opp = sect2->fCurve; do { tStep *= 0.5; work.fStartT += tStep; if (flip) { tStep = -tStep; flip = false; } work.initBounds(fCurve); if (work.fCollapsed) { return false; } if (last.approximatelyEqual(work.pointFirst())) { break; } last = work.pointFirst(); work.fCoinStart.setPerp(fCurve, work.fStartT, last, opp); if (work.fCoinStart.isMatch()) { #if DEBUG_T_SECT work.validatePerpPt(work.fCoinStart.perpT(), work.fCoinStart.perpPt()); #endif double oppTTest = work.fCoinStart.perpT(); if (sect2->fHead->contains(oppTTest)) { *oppT = oppTTest; oppPt = work.fCoinStart.perpPt(); contained = true; if (down ? result <= work.fStartT : result >= work.fStartT) { *oppFirst = nullptr; // signal caller to fail return false; } result = work.fStartT; continue; } } tStep = -tStep; flip = true; } while (true); if (!contained) { return false; } if (last.approximatelyEqual(fCurve[0])) { result = 0; } else if (last.approximatelyEqual(this->pointLast())) { result = 1; } if (oppPt.approximatelyEqual(opp[0])) { *oppT = 0; } else if (oppPt.approximatelyEqual(sect2->pointLast())) { *oppT = 1; } *resultT = result; return true; } // OPTIMIZE ? keep a sorted list of sizes in the form of a doubly-linked list in quad span // so that each quad sect has a pointer to the largest, and can update it as spans // are split SkTSpan* SkTSect::boundsMax() { SkTSpan* test = fHead; SkTSpan* largest = fHead; bool lCollapsed = largest->fCollapsed; int safetyNet = 10000; while ((test = test->fNext)) { if (!--safetyNet) { fHung = true; return nullptr; } bool tCollapsed = test->fCollapsed; if ((lCollapsed && !tCollapsed) || (lCollapsed == tCollapsed && largest->fBoundsMax < test->fBoundsMax)) { largest = test; lCollapsed = test->fCollapsed; } } return largest; } bool SkTSect::coincidentCheck(SkTSect* sect2) { SkTSpan* first = fHead; if (!first) { return false; } SkTSpan* last, * next; do { int consecutive = this->countConsecutiveSpans(first, &last); next = last->fNext; if (consecutive < COINCIDENT_SPAN_COUNT) { continue; } this->validate(); sect2->validate(); this->computePerpendiculars(sect2, first, last); this->validate(); sect2->validate(); // check to see if a range of points are on the curve SkTSpan* coinStart = first; do { bool success = this->extractCoincident(sect2, coinStart, last, &coinStart); if (!success) { return false; } } while (coinStart && !last->fDeleted); if (!fHead || !sect2->fHead) { break; } if (!next || next->fDeleted) { break; } } while ((first = next)); return true; } void SkTSect::coincidentForce(SkTSect* sect2, double start1s, double start1e) { SkTSpan* first = fHead; SkTSpan* last = this->tail(); SkTSpan* oppFirst = sect2->fHead; SkTSpan* oppLast = sect2->tail(); if (!last || !oppLast) { return; } bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); this->removeSpanRange(first, last); sect2->removeSpanRange(oppFirst, oppLast); first->fStartT = start1s; first->fEndT = start1e; first->resetBounds(fCurve); first->fCoinStart.setPerp(fCurve, start1s, fCurve[0], sect2->fCurve); first->fCoinEnd.setPerp(fCurve, start1e, this->pointLast(), sect2->fCurve); bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); double oppStartT = first->fCoinStart.perpT() == -1 ? 0 : std::max(0., first->fCoinStart.perpT()); double oppEndT = first->fCoinEnd.perpT() == -1 ? 1 : std::min(1., first->fCoinEnd.perpT()); if (!oppMatched) { using std::swap; swap(oppStartT, oppEndT); } oppFirst->fStartT = oppStartT; oppFirst->fEndT = oppEndT; oppFirst->resetBounds(sect2->fCurve); this->removeCoincident(first, false); sect2->removeCoincident(oppFirst, true); if (deleteEmptySpans) { this->deleteEmptySpans(); sect2->deleteEmptySpans(); } } bool SkTSect::coincidentHasT(double t) { SkTSpan* test = fCoincident; while (test) { if (between(test->fStartT, t, test->fEndT)) { return true; } test = test->fNext; } return false; } int SkTSect::collapsed() const { int result = 0; const SkTSpan* test = fHead; while (test) { if (test->fCollapsed) { ++result; } test = test->next(); } return result; } void SkTSect::computePerpendiculars(SkTSect* sect2, SkTSpan* first, SkTSpan* last) { if (!last) { return; } const SkTCurve& opp = sect2->fCurve; SkTSpan* work = first; SkTSpan* prior = nullptr; do { if (!work->fHasPerp && !work->fCollapsed) { if (prior) { work->fCoinStart = prior->fCoinEnd; } else { work->fCoinStart.setPerp(fCurve, work->fStartT, work->pointFirst(), opp); } if (work->fCoinStart.isMatch()) { double perpT = work->fCoinStart.perpT(); if (sect2->coincidentHasT(perpT)) { work->fCoinStart.init(); } else { sect2->addForPerp(work, perpT); } } work->fCoinEnd.setPerp(fCurve, work->fEndT, work->pointLast(), opp); if (work->fCoinEnd.isMatch()) { double perpT = work->fCoinEnd.perpT(); if (sect2->coincidentHasT(perpT)) { work->fCoinEnd.init(); } else { sect2->addForPerp(work, perpT); } } work->fHasPerp = true; } if (work == last) { break; } prior = work; work = work->fNext; SkASSERT(work); } while (true); } int SkTSect::countConsecutiveSpans(SkTSpan* first, SkTSpan** lastPtr) const { int consecutive = 1; SkTSpan* last = first; do { SkTSpan* next = last->fNext; if (!next) { break; } if (next->fStartT > last->fEndT) { break; } ++consecutive; last = next; } while (true); *lastPtr = last; return consecutive; } bool SkTSect::hasBounded(const SkTSpan* span) const { const SkTSpan* test = fHead; if (!test) { return false; } do { if (test->findOppSpan(span)) { return true; } } while ((test = test->next())); return false; } bool SkTSect::deleteEmptySpans() { SkTSpan* test; SkTSpan* next = fHead; int safetyHatch = 1000; while ((test = next)) { next = test->fNext; if (!test->fBounded) { if (!this->removeSpan(test)) { return false; } } if (--safetyHatch < 0) { return false; } } return true; } bool SkTSect::extractCoincident( SkTSect* sect2, SkTSpan* first, SkTSpan* last, SkTSpan** result) { first = findCoincidentRun(first, &last); if (!first || !last) { *result = nullptr; return true; } // march outwards to find limit of coincidence from here to previous and next spans double startT = first->fStartT; double oppStartT SK_INIT_TO_AVOID_WARNING; double oppEndT SK_INIT_TO_AVOID_WARNING; SkTSpan* prev = first->fPrev; SkASSERT(first->fCoinStart.isMatch()); SkTSpan* oppFirst = first->findOppT(first->fCoinStart.perpT()); SkOPASSERT(last->fCoinEnd.isMatch()); bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); double coinStart; SkDEBUGCODE(double coinEnd); SkTSpan* cutFirst; if (prev && prev->fEndT == startT && this->binarySearchCoin(sect2, startT, prev->fStartT - startT, &coinStart, &oppStartT, &oppFirst) && prev->fStartT < coinStart && coinStart < startT && (cutFirst = prev->oppT(oppStartT))) { oppFirst = cutFirst; first = this->addSplitAt(prev, coinStart); first->markCoincident(); prev->fCoinEnd.markCoincident(); if (oppFirst->fStartT < oppStartT && oppStartT < oppFirst->fEndT) { SkTSpan* oppHalf = sect2->addSplitAt(oppFirst, oppStartT); if (oppMatched) { oppFirst->fCoinEnd.markCoincident(); oppHalf->markCoincident(); oppFirst = oppHalf; } else { oppFirst->markCoincident(); oppHalf->fCoinStart.markCoincident(); } } } else { if (!oppFirst) { return false; } SkDEBUGCODE(coinStart = first->fStartT); SkDEBUGCODE(oppStartT = oppMatched ? oppFirst->fStartT : oppFirst->fEndT); } // FIXME: incomplete : if we're not at the end, find end of coin SkTSpan* oppLast; SkOPASSERT(last->fCoinEnd.isMatch()); oppLast = last->findOppT(last->fCoinEnd.perpT()); SkDEBUGCODE(coinEnd = last->fEndT); #ifdef SK_DEBUG if (!this->globalState() || !this->globalState()->debugSkipAssert()) { oppEndT = oppMatched ? oppLast->fEndT : oppLast->fStartT; } #endif if (!oppMatched) { using std::swap; swap(oppFirst, oppLast); swap(oppStartT, oppEndT); } SkOPASSERT(oppStartT < oppEndT); SkASSERT(coinStart == first->fStartT); SkASSERT(coinEnd == last->fEndT); if (!oppFirst) { *result = nullptr; return true; } SkOPASSERT(oppStartT == oppFirst->fStartT); if (!oppLast) { *result = nullptr; return true; } SkOPASSERT(oppEndT == oppLast->fEndT); // reduce coincident runs to single entries this->validate(); sect2->validate(); bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); this->removeSpanRange(first, last); sect2->removeSpanRange(oppFirst, oppLast); first->fEndT = last->fEndT; first->resetBounds(this->fCurve); first->fCoinStart.setPerp(fCurve, first->fStartT, first->pointFirst(), sect2->fCurve); first->fCoinEnd.setPerp(fCurve, first->fEndT, first->pointLast(), sect2->fCurve); oppStartT = first->fCoinStart.perpT(); oppEndT = first->fCoinEnd.perpT(); if (between(0, oppStartT, 1) && between(0, oppEndT, 1)) { if (!oppMatched) { using std::swap; swap(oppStartT, oppEndT); } oppFirst->fStartT = oppStartT; oppFirst->fEndT = oppEndT; oppFirst->resetBounds(sect2->fCurve); } this->validateBounded(); sect2->validateBounded(); last = first->fNext; if (!this->removeCoincident(first, false)) { return false; } if (!sect2->removeCoincident(oppFirst, true)) { return false; } if (deleteEmptySpans) { if (!this->deleteEmptySpans() || !sect2->deleteEmptySpans()) { *result = nullptr; return false; } } this->validate(); sect2->validate(); *result = last && !last->fDeleted && fHead && sect2->fHead ? last : nullptr; return true; } SkTSpan* SkTSect::findCoincidentRun( SkTSpan* first, SkTSpan** lastPtr) { SkTSpan* work = first; SkTSpan* lastCandidate = nullptr; first = nullptr; // find the first fully coincident span do { if (work->fCoinStart.isMatch()) { #if DEBUG_T_SECT work->validatePerpT(work->fCoinStart.perpT()); work->validatePerpPt(work->fCoinStart.perpT(), work->fCoinStart.perpPt()); #endif SkOPASSERT(work->hasOppT(work->fCoinStart.perpT())); if (!work->fCoinEnd.isMatch()) { break; } lastCandidate = work; if (!first) { first = work; } } else if (first && work->fCollapsed) { *lastPtr = lastCandidate; return first; } else { lastCandidate = nullptr; SkOPASSERT(!first); } if (work == *lastPtr) { return first; } work = work->fNext; if (!work) { return nullptr; } } while (true); if (lastCandidate) { *lastPtr = lastCandidate; } return first; } int SkTSect::intersects(SkTSpan* span, SkTSect* opp, SkTSpan* oppSpan, int* oppResult) { bool spanStart, oppStart; int hullResult = span->hullsIntersect(oppSpan, &spanStart, &oppStart); if (hullResult >= 0) { if (hullResult == 2) { // hulls have one point in common if (!span->fBounded || !span->fBounded->fNext) { SkASSERT(!span->fBounded || span->fBounded->fBounded == oppSpan); if (spanStart) { span->fEndT = span->fStartT; } else { span->fStartT = span->fEndT; } } else { hullResult = 1; } if (!oppSpan->fBounded || !oppSpan->fBounded->fNext) { if (oppSpan->fBounded && oppSpan->fBounded->fBounded != span) { return 0; } if (oppStart) { oppSpan->fEndT = oppSpan->fStartT; } else { oppSpan->fStartT = oppSpan->fEndT; } *oppResult = 2; } else { *oppResult = 1; } } else { *oppResult = 1; } return hullResult; } if (span->fIsLine && oppSpan->fIsLine) { SkIntersections i; int sects = this->linesIntersect(span, opp, oppSpan, &i); if (sects == 2) { return *oppResult = 1; } if (!sects) { return -1; } this->removedEndCheck(span); span->fStartT = span->fEndT = i[0][0]; opp->removedEndCheck(oppSpan); oppSpan->fStartT = oppSpan->fEndT = i[1][0]; return *oppResult = 2; } if (span->fIsLinear || oppSpan->fIsLinear) { return *oppResult = (int) span->linearsIntersect(oppSpan); } return *oppResult = 1; } template static bool is_parallel(const SkDLine& thisLine, const SkTCurve& opp) { if (!opp.IsConic()) { return false; // FIXME : breaks a lot of stuff now } int finds = 0; SkDLine thisPerp; thisPerp.fPts[0].fX = thisLine.fPts[1].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); thisPerp.fPts[0].fY = thisLine.fPts[1].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); thisPerp.fPts[1] = thisLine.fPts[1]; SkIntersections perpRayI; perpRayI.intersectRay(opp, thisPerp); for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[1]); } thisPerp.fPts[1].fX = thisLine.fPts[0].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); thisPerp.fPts[1].fY = thisLine.fPts[0].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); thisPerp.fPts[0] = thisLine.fPts[0]; perpRayI.intersectRay(opp, thisPerp); for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[0]); } return finds >= 2; } // while the intersection points are sufficiently far apart: // construct the tangent lines from the intersections // find the point where the tangent line intersects the opposite curve int SkTSect::linesIntersect(SkTSpan* span, SkTSect* opp, SkTSpan* oppSpan, SkIntersections* i) { SkIntersections thisRayI SkDEBUGCODE((span->fDebugGlobalState)); SkIntersections oppRayI SkDEBUGCODE((span->fDebugGlobalState)); SkDLine thisLine = {{ span->pointFirst(), span->pointLast() }}; SkDLine oppLine = {{ oppSpan->pointFirst(), oppSpan->pointLast() }}; int loopCount = 0; double bestDistSq = DBL_MAX; if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { return 0; } if (!oppRayI.intersectRay(this->fCurve, oppLine)) { return 0; } // if the ends of each line intersect the opposite curve, the lines are coincident if (thisRayI.used() > 1) { int ptMatches = 0; for (int tIndex = 0; tIndex < thisRayI.used(); ++tIndex) { for (int lIndex = 0; lIndex < (int) std::size(thisLine.fPts); ++lIndex) { ptMatches += thisRayI.pt(tIndex).approximatelyEqual(thisLine.fPts[lIndex]); } } if (ptMatches == 2 || is_parallel(thisLine, opp->fCurve)) { return 2; } } if (oppRayI.used() > 1) { int ptMatches = 0; for (int oIndex = 0; oIndex < oppRayI.used(); ++oIndex) { for (int lIndex = 0; lIndex < (int) std::size(oppLine.fPts); ++lIndex) { ptMatches += oppRayI.pt(oIndex).approximatelyEqual(oppLine.fPts[lIndex]); } } if (ptMatches == 2|| is_parallel(oppLine, this->fCurve)) { return 2; } } do { // pick the closest pair of points double closest = DBL_MAX; int closeIndex SK_INIT_TO_AVOID_WARNING; int oppCloseIndex SK_INIT_TO_AVOID_WARNING; for (int index = 0; index < oppRayI.used(); ++index) { if (!roughly_between(span->fStartT, oppRayI[0][index], span->fEndT)) { continue; } for (int oIndex = 0; oIndex < thisRayI.used(); ++oIndex) { if (!roughly_between(oppSpan->fStartT, thisRayI[0][oIndex], oppSpan->fEndT)) { continue; } double distSq = thisRayI.pt(index).distanceSquared(oppRayI.pt(oIndex)); if (closest > distSq) { closest = distSq; closeIndex = index; oppCloseIndex = oIndex; } } } if (closest == DBL_MAX) { break; } const SkDPoint& oppIPt = thisRayI.pt(oppCloseIndex); const SkDPoint& iPt = oppRayI.pt(closeIndex); if (between(span->fStartT, oppRayI[0][closeIndex], span->fEndT) && between(oppSpan->fStartT, thisRayI[0][oppCloseIndex], oppSpan->fEndT) && oppIPt.approximatelyEqual(iPt)) { i->merge(oppRayI, closeIndex, thisRayI, oppCloseIndex); return i->used(); } double distSq = oppIPt.distanceSquared(iPt); if (bestDistSq < distSq || ++loopCount > 5) { return 0; } bestDistSq = distSq; double oppStart = oppRayI[0][closeIndex]; thisLine[0] = fCurve.ptAtT(oppStart); thisLine[1] = thisLine[0] + fCurve.dxdyAtT(oppStart); if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { break; } double start = thisRayI[0][oppCloseIndex]; oppLine[0] = opp->fCurve.ptAtT(start); oppLine[1] = oppLine[0] + opp->fCurve.dxdyAtT(start); if (!oppRayI.intersectRay(this->fCurve, oppLine)) { break; } } while (true); // convergence may fail if the curves are nearly coincident SkTCoincident oCoinS, oCoinE; oCoinS.setPerp(opp->fCurve, oppSpan->fStartT, oppSpan->pointFirst(), fCurve); oCoinE.setPerp(opp->fCurve, oppSpan->fEndT, oppSpan->pointLast(), fCurve); double tStart = oCoinS.perpT(); double tEnd = oCoinE.perpT(); bool swap = tStart > tEnd; if (swap) { using std::swap; swap(tStart, tEnd); } tStart = std::max(tStart, span->fStartT); tEnd = std::min(tEnd, span->fEndT); if (tStart > tEnd) { return 0; } SkDVector perpS, perpE; if (tStart == span->fStartT) { SkTCoincident coinS; coinS.setPerp(fCurve, span->fStartT, span->pointFirst(), opp->fCurve); perpS = span->pointFirst() - coinS.perpPt(); } else if (swap) { perpS = oCoinE.perpPt() - oppSpan->pointLast(); } else { perpS = oCoinS.perpPt() - oppSpan->pointFirst(); } if (tEnd == span->fEndT) { SkTCoincident coinE; coinE.setPerp(fCurve, span->fEndT, span->pointLast(), opp->fCurve); perpE = span->pointLast() - coinE.perpPt(); } else if (swap) { perpE = oCoinS.perpPt() - oppSpan->pointFirst(); } else { perpE = oCoinE.perpPt() - oppSpan->pointLast(); } if (perpS.dot(perpE) >= 0) { return 0; } SkTCoincident coinW; double workT = tStart; double tStep = tEnd - tStart; SkDPoint workPt; do { tStep *= 0.5; if (precisely_zero(tStep)) { return 0; } workT += tStep; workPt = fCurve.ptAtT(workT); coinW.setPerp(fCurve, workT, workPt, opp->fCurve); double perpT = coinW.perpT(); if (coinW.isMatch() ? !between(oppSpan->fStartT, perpT, oppSpan->fEndT) : perpT < 0) { continue; } SkDVector perpW = workPt - coinW.perpPt(); if ((perpS.dot(perpW) >= 0) == (tStep < 0)) { tStep = -tStep; } if (workPt.approximatelyEqual(coinW.perpPt())) { break; } } while (true); double oppTTest = coinW.perpT(); if (!opp->fHead->contains(oppTTest)) { return 0; } i->setMax(1); i->insert(workT, oppTTest, workPt); return 1; } bool SkTSect::markSpanGone(SkTSpan* span) { if (--fActiveCount < 0) { return false; } span->fNext = fDeleted; fDeleted = span; SkOPASSERT(!span->fDeleted); span->fDeleted = true; return true; } bool SkTSect::matchedDirection(double t, const SkTSect* sect2, double t2) const { SkDVector dxdy = this->fCurve.dxdyAtT(t); SkDVector dxdy2 = sect2->fCurve.dxdyAtT(t2); return dxdy.dot(dxdy2) >= 0; } void SkTSect::matchedDirCheck(double t, const SkTSect* sect2, double t2, bool* calcMatched, bool* oppMatched) const { if (*calcMatched) { SkASSERT(*oppMatched == this->matchedDirection(t, sect2, t2)); } else { *oppMatched = this->matchedDirection(t, sect2, t2); *calcMatched = true; } } void SkTSect::mergeCoincidence(SkTSect* sect2) { double smallLimit = 0; do { // find the smallest unprocessed span SkTSpan* smaller = nullptr; SkTSpan* test = fCoincident; do { if (!test) { return; } if (test->fStartT < smallLimit) { continue; } if (smaller && smaller->fEndT < test->fStartT) { continue; } smaller = test; } while ((test = test->fNext)); if (!smaller) { return; } smallLimit = smaller->fEndT; // find next larger span SkTSpan* prior = nullptr; SkTSpan* larger = nullptr; SkTSpan* largerPrior = nullptr; test = fCoincident; do { if (test->fStartT < smaller->fEndT) { continue; } SkOPASSERT(test->fStartT != smaller->fEndT); if (larger && larger->fStartT < test->fStartT) { continue; } largerPrior = prior; larger = test; } while ((void) (prior = test), (test = test->fNext)); if (!larger) { continue; } // check middle t value to see if it is coincident as well double midT = (smaller->fEndT + larger->fStartT) / 2; SkDPoint midPt = fCurve.ptAtT(midT); SkTCoincident coin; coin.setPerp(fCurve, midT, midPt, sect2->fCurve); if (coin.isMatch()) { smaller->fEndT = larger->fEndT; smaller->fCoinEnd = larger->fCoinEnd; if (largerPrior) { largerPrior->fNext = larger->fNext; largerPrior->validate(); } else { fCoincident = larger->fNext; } } } while (true); } SkTSpan* SkTSect::prev( SkTSpan* span) const { SkTSpan* result = nullptr; SkTSpan* test = fHead; while (span != test) { result = test; test = test->fNext; SkASSERT(test); } return result; } void SkTSect::recoverCollapsed() { SkTSpan* deleted = fDeleted; while (deleted) { SkTSpan* delNext = deleted->fNext; if (deleted->fCollapsed) { SkTSpan** spanPtr = &fHead; while (*spanPtr && (*spanPtr)->fEndT <= deleted->fStartT) { spanPtr = &(*spanPtr)->fNext; } deleted->fNext = *spanPtr; *spanPtr = deleted; } deleted = delNext; } } void SkTSect::removeAllBut(const SkTSpan* keep, SkTSpan* span, SkTSect* opp) { const SkTSpanBounded* testBounded = span->fBounded; while (testBounded) { SkTSpan* bounded = testBounded->fBounded; const SkTSpanBounded* next = testBounded->fNext; // may have been deleted when opp did 'remove all but' if (bounded != keep && !bounded->fDeleted) { SkAssertResult(SkDEBUGCODE(!) span->removeBounded(bounded)); if (bounded->removeBounded(span)) { opp->removeSpan(bounded); } } testBounded = next; } SkASSERT(!span->fDeleted); SkASSERT(span->findOppSpan(keep)); SkASSERT(keep->findOppSpan(span)); } bool SkTSect::removeByPerpendicular(SkTSect* opp) { SkTSpan* test = fHead; SkTSpan* next; do { next = test->fNext; if (test->fCoinStart.perpT() < 0 || test->fCoinEnd.perpT() < 0) { continue; } SkDVector startV = test->fCoinStart.perpPt() - test->pointFirst(); SkDVector endV = test->fCoinEnd.perpPt() - test->pointLast(); #if DEBUG_T_SECT SkDebugf("%s startV=(%1.9g,%1.9g) endV=(%1.9g,%1.9g) dot=%1.9g\n", __FUNCTION__, startV.fX, startV.fY, endV.fX, endV.fY, startV.dot(endV)); #endif if (startV.dot(endV) <= 0) { continue; } if (!this->removeSpans(test, opp)) { return false; } } while ((test = next)); return true; } bool SkTSect::removeCoincident(SkTSpan* span, bool isBetween) { if (!this->unlinkSpan(span)) { return false; } if (isBetween || between(0, span->fCoinStart.perpT(), 1)) { --fActiveCount; span->fNext = fCoincident; fCoincident = span; } else { this->markSpanGone(span); } return true; } void SkTSect::removedEndCheck(SkTSpan* span) { if (!span->fStartT) { fRemovedStartT = true; } if (1 == span->fEndT) { fRemovedEndT = true; } } bool SkTSect::removeSpan(SkTSpan* span) {\ this->removedEndCheck(span); if (!this->unlinkSpan(span)) { return false; } return this->markSpanGone(span); } void SkTSect::removeSpanRange(SkTSpan* first, SkTSpan* last) { if (first == last) { return; } SkTSpan* span = first; SkASSERT(span); SkTSpan* final = last->fNext; SkTSpan* next = span->fNext; while ((span = next) && span != final) { next = span->fNext; this->markSpanGone(span); } if (final) { final->fPrev = first; } first->fNext = final; // world may not be ready for validation here first->validate(); } bool SkTSect::removeSpans(SkTSpan* span, SkTSect* opp) { SkTSpanBounded* bounded = span->fBounded; while (bounded) { SkTSpan* spanBounded = bounded->fBounded; SkTSpanBounded* next = bounded->fNext; if (span->removeBounded(spanBounded)) { // shuffles last into position 0 this->removeSpan(span); } if (spanBounded->removeBounded(span)) { opp->removeSpan(spanBounded); } if (span->fDeleted && opp->hasBounded(span)) { return false; } bounded = next; } return true; } SkTSpan* SkTSect::spanAtT(double t, SkTSpan** priorSpan) { SkTSpan* test = fHead; SkTSpan* prev = nullptr; while (test && test->fEndT < t) { prev = test; test = test->fNext; } *priorSpan = prev; return test && test->fStartT <= t ? test : nullptr; } SkTSpan* SkTSect::tail() { SkTSpan* result = fHead; SkTSpan* next = fHead; int safetyNet = 100000; while ((next = next->fNext)) { if (!--safetyNet) { return nullptr; } if (next->fEndT > result->fEndT) { result = next; } } return result; } /* Each span has a range of opposite spans it intersects. After the span is split in two, adjust the range to its new size */ bool SkTSect::trim(SkTSpan* span, SkTSect* opp) { FAIL_IF(!span->initBounds(fCurve)); const SkTSpanBounded* testBounded = span->fBounded; while (testBounded) { SkTSpan* test = testBounded->fBounded; const SkTSpanBounded* next = testBounded->fNext; int oppSects, sects = this->intersects(span, opp, test, &oppSects); if (sects >= 1) { if (oppSects == 2) { test->initBounds(opp->fCurve); opp->removeAllBut(span, test, this); } if (sects == 2) { span->initBounds(fCurve); this->removeAllBut(test, span, opp); return true; } } else { if (span->removeBounded(test)) { this->removeSpan(span); } if (test->removeBounded(span)) { opp->removeSpan(test); } } testBounded = next; } return true; } bool SkTSect::unlinkSpan(SkTSpan* span) { SkTSpan* prev = span->fPrev; SkTSpan* next = span->fNext; if (prev) { prev->fNext = next; if (next) { next->fPrev = prev; if (next->fStartT > next->fEndT) { return false; } // world may not be ready for validate here next->validate(); } } else { fHead = next; if (next) { next->fPrev = nullptr; } } return true; } bool SkTSect::updateBounded(SkTSpan* first, SkTSpan* last, SkTSpan* oppFirst) { SkTSpan* test = first; const SkTSpan* final = last->next(); bool deleteSpan = false; do { deleteSpan |= test->removeAllBounded(); } while ((test = test->fNext) != final && test); first->fBounded = nullptr; first->addBounded(oppFirst, &fHeap); // cannot call validate until remove span range is called return deleteSpan; } void SkTSect::validate() const { #if DEBUG_VALIDATE int count = 0; double last = 0; if (fHead) { const SkTSpan* span = fHead; SkASSERT(!span->fPrev); const SkTSpan* next; do { span->validate(); SkASSERT(span->fStartT >= last); last = span->fEndT; ++count; next = span->fNext; SkASSERT(next != span); } while ((span = next) != nullptr); } SkASSERT(count == fActiveCount); #endif #if DEBUG_T_SECT SkASSERT(fActiveCount <= fDebugAllocatedCount); int deletedCount = 0; const SkTSpan* deleted = fDeleted; while (deleted) { ++deletedCount; deleted = deleted->fNext; } const SkTSpan* coincident = fCoincident; while (coincident) { ++deletedCount; coincident = coincident->fNext; } SkASSERT(fActiveCount + deletedCount == fDebugAllocatedCount); #endif } void SkTSect::validateBounded() const { #if DEBUG_VALIDATE if (!fHead) { return; } const SkTSpan* span = fHead; do { span->validateBounded(); } while ((span = span->fNext) != nullptr); #endif } int SkTSect::EndsEqual(const SkTSect* sect1, const SkTSect* sect2, SkIntersections* intersections) { int zeroOneSet = 0; if (sect1->fCurve[0] == sect2->fCurve[0]) { zeroOneSet |= kZeroS1Set | kZeroS2Set; intersections->insert(0, 0, sect1->fCurve[0]); } if (sect1->fCurve[0] == sect2->pointLast()) { zeroOneSet |= kZeroS1Set | kOneS2Set; intersections->insert(0, 1, sect1->fCurve[0]); } if (sect1->pointLast() == sect2->fCurve[0]) { zeroOneSet |= kOneS1Set | kZeroS2Set; intersections->insert(1, 0, sect1->pointLast()); } if (sect1->pointLast() == sect2->pointLast()) { zeroOneSet |= kOneS1Set | kOneS2Set; intersections->insert(1, 1, sect1->pointLast()); } // check for zero if (!(zeroOneSet & (kZeroS1Set | kZeroS2Set)) && sect1->fCurve[0].approximatelyEqual(sect2->fCurve[0])) { zeroOneSet |= kZeroS1Set | kZeroS2Set; intersections->insertNear(0, 0, sect1->fCurve[0], sect2->fCurve[0]); } if (!(zeroOneSet & (kZeroS1Set | kOneS2Set)) && sect1->fCurve[0].approximatelyEqual(sect2->pointLast())) { zeroOneSet |= kZeroS1Set | kOneS2Set; intersections->insertNear(0, 1, sect1->fCurve[0], sect2->pointLast()); } // check for one if (!(zeroOneSet & (kOneS1Set | kZeroS2Set)) && sect1->pointLast().approximatelyEqual(sect2->fCurve[0])) { zeroOneSet |= kOneS1Set | kZeroS2Set; intersections->insertNear(1, 0, sect1->pointLast(), sect2->fCurve[0]); } if (!(zeroOneSet & (kOneS1Set | kOneS2Set)) && sect1->pointLast().approximatelyEqual(sect2->pointLast())) { zeroOneSet |= kOneS1Set | kOneS2Set; intersections->insertNear(1, 1, sect1->pointLast(), sect2->pointLast()); } return zeroOneSet; } struct SkClosestRecord { bool operator<(const SkClosestRecord& rh) const { return fClosest < rh.fClosest; } void addIntersection(SkIntersections* intersections) const { double r1t = fC1Index ? fC1Span->endT() : fC1Span->startT(); double r2t = fC2Index ? fC2Span->endT() : fC2Span->startT(); intersections->insert(r1t, r2t, fC1Span->part()[fC1Index]); } void findEnd(const SkTSpan* span1, const SkTSpan* span2, int c1Index, int c2Index) { const SkTCurve& c1 = span1->part(); const SkTCurve& c2 = span2->part(); if (!c1[c1Index].approximatelyEqual(c2[c2Index])) { return; } double dist = c1[c1Index].distanceSquared(c2[c2Index]); if (fClosest < dist) { return; } fC1Span = span1; fC2Span = span2; fC1StartT = span1->startT(); fC1EndT = span1->endT(); fC2StartT = span2->startT(); fC2EndT = span2->endT(); fC1Index = c1Index; fC2Index = c2Index; fClosest = dist; } bool matesWith(const SkClosestRecord& mate SkDEBUGPARAMS(SkIntersections* i)) const { SkOPOBJASSERT(i, fC1Span == mate.fC1Span || fC1Span->endT() <= mate.fC1Span->startT() || mate.fC1Span->endT() <= fC1Span->startT()); SkOPOBJASSERT(i, fC2Span == mate.fC2Span || fC2Span->endT() <= mate.fC2Span->startT() || mate.fC2Span->endT() <= fC2Span->startT()); return fC1Span == mate.fC1Span || fC1Span->endT() == mate.fC1Span->startT() || fC1Span->startT() == mate.fC1Span->endT() || fC2Span == mate.fC2Span || fC2Span->endT() == mate.fC2Span->startT() || fC2Span->startT() == mate.fC2Span->endT(); } void merge(const SkClosestRecord& mate) { fC1Span = mate.fC1Span; fC2Span = mate.fC2Span; fClosest = mate.fClosest; fC1Index = mate.fC1Index; fC2Index = mate.fC2Index; } void reset() { fClosest = FLT_MAX; SkDEBUGCODE(fC1Span = nullptr); SkDEBUGCODE(fC2Span = nullptr); SkDEBUGCODE(fC1Index = fC2Index = -1); } void update(const SkClosestRecord& mate) { fC1StartT = std::min(fC1StartT, mate.fC1StartT); fC1EndT = std::max(fC1EndT, mate.fC1EndT); fC2StartT = std::min(fC2StartT, mate.fC2StartT); fC2EndT = std::max(fC2EndT, mate.fC2EndT); } const SkTSpan* fC1Span; const SkTSpan* fC2Span; double fC1StartT; double fC1EndT; double fC2StartT; double fC2EndT; double fClosest; int fC1Index; int fC2Index; }; struct SkClosestSect { SkClosestSect() : fUsed(0) { fClosest.push_back().reset(); } bool find(const SkTSpan* span1, const SkTSpan* span2 SkDEBUGPARAMS(SkIntersections* i)) { SkClosestRecord* record = &fClosest[fUsed]; record->findEnd(span1, span2, 0, 0); record->findEnd(span1, span2, 0, span2->part().pointLast()); record->findEnd(span1, span2, span1->part().pointLast(), 0); record->findEnd(span1, span2, span1->part().pointLast(), span2->part().pointLast()); if (record->fClosest == FLT_MAX) { return false; } for (int index = 0; index < fUsed; ++index) { SkClosestRecord* test = &fClosest[index]; if (test->matesWith(*record SkDEBUGPARAMS(i))) { if (test->fClosest > record->fClosest) { test->merge(*record); } test->update(*record); record->reset(); return false; } } ++fUsed; fClosest.push_back().reset(); return true; } void finish(SkIntersections* intersections) const { STArray closestPtrs; for (int index = 0; index < fUsed; ++index) { closestPtrs.push_back(&fClosest[index]); } SkTQSort(closestPtrs.begin(), closestPtrs.end()); for (int index = 0; index < fUsed; ++index) { const SkClosestRecord* test = closestPtrs[index]; test->addIntersection(intersections); } } // this is oversized so that an extra records can merge into final one STArray fClosest; int fUsed; }; // returns true if the rect is too small to consider void SkTSect::BinarySearch(SkTSect* sect1, SkTSect* sect2, SkIntersections* intersections) { #if DEBUG_T_SECT_DUMP > 1 gDumpTSectNum = 0; #endif SkDEBUGCODE(sect1->fOppSect = sect2); SkDEBUGCODE(sect2->fOppSect = sect1); intersections->reset(); intersections->setMax(sect1->fCurve.maxIntersections() + 4); // give extra for slop SkTSpan* span1 = sect1->fHead; SkTSpan* span2 = sect2->fHead; int oppSect, sect = sect1->intersects(span1, sect2, span2, &oppSect); // SkASSERT(between(0, sect, 2)); if (!sect) { return; } if (sect == 2 && oppSect == 2) { (void) EndsEqual(sect1, sect2, intersections); return; } span1->addBounded(span2, §1->fHeap); span2->addBounded(span1, §2->fHeap); const int kMaxCoinLoopCount = 8; int coinLoopCount = kMaxCoinLoopCount; double start1s SK_INIT_TO_AVOID_WARNING; double start1e SK_INIT_TO_AVOID_WARNING; do { // find the largest bounds SkTSpan* largest1 = sect1->boundsMax(); if (!largest1) { if (sect1->fHung) { return; } break; } SkTSpan* largest2 = sect2->boundsMax(); // split it if (!largest2 || (largest1 && (largest1->fBoundsMax > largest2->fBoundsMax || (!largest1->fCollapsed && largest2->fCollapsed)))) { if (sect2->fHung) { return; } if (largest1->fCollapsed) { break; } sect1->resetRemovedEnds(); sect2->resetRemovedEnds(); // trim parts that don't intersect the opposite SkTSpan* half1 = sect1->addOne(); SkDEBUGCODE(half1->debugSetGlobalState(sect1->globalState())); if (!half1->split(largest1, §1->fHeap)) { break; } if (!sect1->trim(largest1, sect2)) { SkOPOBJASSERT(intersections, 0); return; } if (!sect1->trim(half1, sect2)) { SkOPOBJASSERT(intersections, 0); return; } } else { if (largest2->fCollapsed) { break; } sect1->resetRemovedEnds(); sect2->resetRemovedEnds(); // trim parts that don't intersect the opposite SkTSpan* half2 = sect2->addOne(); SkDEBUGCODE(half2->debugSetGlobalState(sect2->globalState())); if (!half2->split(largest2, §2->fHeap)) { break; } if (!sect2->trim(largest2, sect1)) { SkOPOBJASSERT(intersections, 0); return; } if (!sect2->trim(half2, sect1)) { SkOPOBJASSERT(intersections, 0); return; } } sect1->validate(); sect2->validate(); #if DEBUG_T_SECT_LOOP_COUNT intersections->debugBumpLoopCount(SkIntersections::kIterations_DebugLoop); #endif // if there are 9 or more continuous spans on both sects, suspect coincidence if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { if (coinLoopCount == kMaxCoinLoopCount) { start1s = sect1->fHead->fStartT; start1e = sect1->tail()->fEndT; } if (!sect1->coincidentCheck(sect2)) { return; } sect1->validate(); sect2->validate(); #if DEBUG_T_SECT_LOOP_COUNT intersections->debugBumpLoopCount(SkIntersections::kCoinCheck_DebugLoop); #endif if (!--coinLoopCount && sect1->fHead && sect2->fHead) { /* All known working cases resolve in two tries. Sadly, cubicConicTests[0] gets stuck in a loop. It adds an extension to allow a coincident end perpendicular to track its intersection in the opposite curve. However, the bounding box of the extension does not intersect the original curve, so the extension is discarded, only to be added again the next time around. */ sect1->coincidentForce(sect2, start1s, start1e); sect1->validate(); sect2->validate(); } } if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { if (!sect1->fHead) { return; } sect1->computePerpendiculars(sect2, sect1->fHead, sect1->tail()); if (!sect2->fHead) { return; } sect2->computePerpendiculars(sect1, sect2->fHead, sect2->tail()); if (!sect1->removeByPerpendicular(sect2)) { return; } sect1->validate(); sect2->validate(); #if DEBUG_T_SECT_LOOP_COUNT intersections->debugBumpLoopCount(SkIntersections::kComputePerp_DebugLoop); #endif if (sect1->collapsed() > sect1->fCurve.maxIntersections()) { break; } } #if DEBUG_T_SECT_DUMP sect1->dumpBoth(sect2); #endif if (!sect1->fHead || !sect2->fHead) { break; } } while (true); SkTSpan* coincident = sect1->fCoincident; if (coincident) { // if there is more than one coincident span, check loosely to see if they should be joined if (coincident->fNext) { sect1->mergeCoincidence(sect2); coincident = sect1->fCoincident; } SkASSERT(sect2->fCoincident); // courtesy check : coincidence only looks at sect 1 do { if (!coincident) { return; } if (!coincident->fCoinStart.isMatch()) { continue; } if (!coincident->fCoinEnd.isMatch()) { continue; } double perpT = coincident->fCoinStart.perpT(); if (perpT < 0) { return; } int index = intersections->insertCoincident(coincident->fStartT, perpT, coincident->pointFirst()); if ((intersections->insertCoincident(coincident->fEndT, coincident->fCoinEnd.perpT(), coincident->pointLast()) < 0) && index >= 0) { intersections->clearCoincidence(index); } } while ((coincident = coincident->fNext)); } int zeroOneSet = EndsEqual(sect1, sect2, intersections); // if (!sect1->fHead || !sect2->fHead) { // if the final iteration contains an end (0 or 1), if (sect1->fRemovedStartT && !(zeroOneSet & kZeroS1Set)) { SkTCoincident perp; // intersect perpendicular with opposite curve perp.setPerp(sect1->fCurve, 0, sect1->fCurve[0], sect2->fCurve); if (perp.isMatch()) { intersections->insert(0, perp.perpT(), perp.perpPt()); } } if (sect1->fRemovedEndT && !(zeroOneSet & kOneS1Set)) { SkTCoincident perp; perp.setPerp(sect1->fCurve, 1, sect1->pointLast(), sect2->fCurve); if (perp.isMatch()) { intersections->insert(1, perp.perpT(), perp.perpPt()); } } if (sect2->fRemovedStartT && !(zeroOneSet & kZeroS2Set)) { SkTCoincident perp; perp.setPerp(sect2->fCurve, 0, sect2->fCurve[0], sect1->fCurve); if (perp.isMatch()) { intersections->insert(perp.perpT(), 0, perp.perpPt()); } } if (sect2->fRemovedEndT && !(zeroOneSet & kOneS2Set)) { SkTCoincident perp; perp.setPerp(sect2->fCurve, 1, sect2->pointLast(), sect1->fCurve); if (perp.isMatch()) { intersections->insert(perp.perpT(), 1, perp.perpPt()); } } // } if (!sect1->fHead || !sect2->fHead) { return; } sect1->recoverCollapsed(); sect2->recoverCollapsed(); SkTSpan* result1 = sect1->fHead; // check heads and tails for zero and ones and insert them if we haven't already done so const SkTSpan* head1 = result1; if (!(zeroOneSet & kZeroS1Set) && approximately_less_than_zero(head1->fStartT)) { const SkDPoint& start1 = sect1->fCurve[0]; if (head1->isBounded()) { double t = head1->closestBoundedT(start1); if (sect2->fCurve.ptAtT(t).approximatelyEqual(start1)) { intersections->insert(0, t, start1); } } } const SkTSpan* head2 = sect2->fHead; if (!(zeroOneSet & kZeroS2Set) && approximately_less_than_zero(head2->fStartT)) { const SkDPoint& start2 = sect2->fCurve[0]; if (head2->isBounded()) { double t = head2->closestBoundedT(start2); if (sect1->fCurve.ptAtT(t).approximatelyEqual(start2)) { intersections->insert(t, 0, start2); } } } if (!(zeroOneSet & kOneS1Set)) { const SkTSpan* tail1 = sect1->tail(); if (!tail1) { return; } if (approximately_greater_than_one(tail1->fEndT)) { const SkDPoint& end1 = sect1->pointLast(); if (tail1->isBounded()) { double t = tail1->closestBoundedT(end1); if (sect2->fCurve.ptAtT(t).approximatelyEqual(end1)) { intersections->insert(1, t, end1); } } } } if (!(zeroOneSet & kOneS2Set)) { const SkTSpan* tail2 = sect2->tail(); if (!tail2) { return; } if (approximately_greater_than_one(tail2->fEndT)) { const SkDPoint& end2 = sect2->pointLast(); if (tail2->isBounded()) { double t = tail2->closestBoundedT(end2); if (sect1->fCurve.ptAtT(t).approximatelyEqual(end2)) { intersections->insert(t, 1, end2); } } } } SkClosestSect closest; do { while (result1 && result1->fCoinStart.isMatch() && result1->fCoinEnd.isMatch()) { result1 = result1->fNext; } if (!result1) { break; } SkTSpan* result2 = sect2->fHead; while (result2) { closest.find(result1, result2 SkDEBUGPARAMS(intersections)); result2 = result2->fNext; } } while ((result1 = result1->fNext)); closest.finish(intersections); // if there is more than one intersection and it isn't already coincident, check int last = intersections->used() - 1; for (int index = 0; index < last; ) { if (intersections->isCoincident(index) && intersections->isCoincident(index + 1)) { ++index; continue; } double midT = ((*intersections)[0][index] + (*intersections)[0][index + 1]) / 2; SkDPoint midPt = sect1->fCurve.ptAtT(midT); // intersect perpendicular with opposite curve SkTCoincident perp; perp.setPerp(sect1->fCurve, midT, midPt, sect2->fCurve); if (!perp.isMatch()) { ++index; continue; } if (intersections->isCoincident(index)) { intersections->removeOne(index); --last; } else if (intersections->isCoincident(index + 1)) { intersections->removeOne(index + 1); --last; } else { intersections->setCoincident(index++); } intersections->setCoincident(index); } SkOPOBJASSERT(intersections, intersections->used() <= sect1->fCurve.maxIntersections()); } int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) { SkTQuad quad1(q1); SkTQuad quad2(q2); SkTSect sect1(quad1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); SkTSect sect2(quad2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); SkTSect::BinarySearch(§1, §2, this); return used(); } int SkIntersections::intersect(const SkDConic& c, const SkDQuad& q) { SkTConic conic(c); SkTQuad quad(q); SkTSect sect1(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); SkTSect::BinarySearch(§1, §2, this); return used(); } int SkIntersections::intersect(const SkDConic& c1, const SkDConic& c2) { SkTConic conic1(c1); SkTConic conic2(c2); SkTSect sect1(conic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); SkTSect sect2(conic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); SkTSect::BinarySearch(§1, §2, this); return used(); } int SkIntersections::intersect(const SkDCubic& c, const SkDQuad& q) { SkTCubic cubic(c); SkTQuad quad(q); SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); SkTSect::BinarySearch(§1, §2, this); return used(); } int SkIntersections::intersect(const SkDCubic& cu, const SkDConic& co) { SkTCubic cubic(cu); SkTConic conic(co); SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); SkTSect sect2(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); SkTSect::BinarySearch(§1, §2, this); return used(); } int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) { SkTCubic cubic1(c1); SkTCubic cubic2(c2); SkTSect sect1(cubic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); SkTSect sect2(cubic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); SkTSect::BinarySearch(§1, §2, this); return used(); } static bool arguments_denormalized(float a, float b, int epsilon) { float denormalizedCheck = FLT_EPSILON * epsilon / 2; return fabsf(a) <= denormalizedCheck && fabsf(b) <= denormalizedCheck; } // from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ // FIXME: move to SkFloatBits.h static bool equal_ulps(float a, float b, int epsilon, int depsilon) { if (arguments_denormalized(a, b, depsilon)) { return true; } int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits < bBits + epsilon && bBits < aBits + epsilon; } static bool equal_ulps_no_normal_check(float a, float b, int epsilon, int depsilon) { int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits < bBits + epsilon && bBits < aBits + epsilon; } static bool equal_ulps_pin(float a, float b, int epsilon, int depsilon) { if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) { return false; } if (arguments_denormalized(a, b, depsilon)) { return true; } int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits < bBits + epsilon && bBits < aBits + epsilon; } static bool d_equal_ulps(float a, float b, int epsilon) { int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits < bBits + epsilon && bBits < aBits + epsilon; } static bool not_equal_ulps(float a, float b, int epsilon) { if (arguments_denormalized(a, b, epsilon)) { return false; } int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits >= bBits + epsilon || bBits >= aBits + epsilon; } static bool not_equal_ulps_pin(float a, float b, int epsilon) { if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) { return false; } if (arguments_denormalized(a, b, epsilon)) { return false; } int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits >= bBits + epsilon || bBits >= aBits + epsilon; } static bool d_not_equal_ulps(float a, float b, int epsilon) { int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits >= bBits + epsilon || bBits >= aBits + epsilon; } static bool less_ulps(float a, float b, int epsilon) { if (arguments_denormalized(a, b, epsilon)) { return a <= b - FLT_EPSILON * epsilon; } int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits <= bBits - epsilon; } static bool less_or_equal_ulps(float a, float b, int epsilon) { if (arguments_denormalized(a, b, epsilon)) { return a < b + FLT_EPSILON * epsilon; } int aBits = SkFloatAs2sCompliment(a); int bBits = SkFloatAs2sCompliment(b); // Find the difference in ULPs. return aBits < bBits + epsilon; } // equality using the same error term as between bool AlmostBequalUlps(float a, float b) { const int UlpsEpsilon = 2; return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); } bool AlmostPequalUlps(float a, float b) { const int UlpsEpsilon = 8; return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); } bool AlmostDequalUlps(float a, float b) { const int UlpsEpsilon = 16; return d_equal_ulps(a, b, UlpsEpsilon); } bool AlmostDequalUlps(double a, double b) { if (fabs(a) < SK_ScalarMax && fabs(b) < SK_ScalarMax) { return AlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); } // We allow divide-by-zero here. It only happens if one of a,b is zero, and the other is NaN. // (Otherwise, we'd hit the condition above). Thus, if std::max returns 0, we compute NaN / 0, // which will produce NaN. The comparison will return false, which is the correct answer. return sk_ieee_double_divide(fabs(a - b), std::max(fabs(a), fabs(b))) < FLT_EPSILON * 16; } bool AlmostEqualUlps(float a, float b) { const int UlpsEpsilon = 16; return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); } bool AlmostEqualUlpsNoNormalCheck(float a, float b) { const int UlpsEpsilon = 16; return equal_ulps_no_normal_check(a, b, UlpsEpsilon, UlpsEpsilon); } bool AlmostEqualUlps_Pin(float a, float b) { const int UlpsEpsilon = 16; return equal_ulps_pin(a, b, UlpsEpsilon, UlpsEpsilon); } bool NotAlmostEqualUlps(float a, float b) { const int UlpsEpsilon = 16; return not_equal_ulps(a, b, UlpsEpsilon); } bool NotAlmostEqualUlps_Pin(float a, float b) { const int UlpsEpsilon = 16; return not_equal_ulps_pin(a, b, UlpsEpsilon); } bool NotAlmostDequalUlps(float a, float b) { const int UlpsEpsilon = 16; return d_not_equal_ulps(a, b, UlpsEpsilon); } bool RoughlyEqualUlps(float a, float b) { const int UlpsEpsilon = 256; const int DUlpsEpsilon = 1024; return equal_ulps(a, b, UlpsEpsilon, DUlpsEpsilon); } bool AlmostBetweenUlps(float a, float b, float c) { const int UlpsEpsilon = 2; return a <= c ? less_or_equal_ulps(a, b, UlpsEpsilon) && less_or_equal_ulps(b, c, UlpsEpsilon) : less_or_equal_ulps(b, a, UlpsEpsilon) && less_or_equal_ulps(c, b, UlpsEpsilon); } bool AlmostLessUlps(float a, float b) { const int UlpsEpsilon = 16; return less_ulps(a, b, UlpsEpsilon); } bool AlmostLessOrEqualUlps(float a, float b) { const int UlpsEpsilon = 16; return less_or_equal_ulps(a, b, UlpsEpsilon); } int UlpsDistance(float a, float b) { SkFloatIntUnion floatIntA, floatIntB; floatIntA.fFloat = a; floatIntB.fFloat = b; // Different signs means they do not match. if ((floatIntA.fSignBitInt < 0) != (floatIntB.fSignBitInt < 0)) { // Check for equality to make sure +0 == -0 return a == b ? 0 : SK_MaxS32; } // Find the difference in ULPs. return SkTAbs(floatIntA.fSignBitInt - floatIntB.fSignBitInt); } SkOpGlobalState::SkOpGlobalState(SkOpContourHead* head, SkArenaAlloc* allocator SkDEBUGPARAMS(bool debugSkipAssert) SkDEBUGPARAMS(const char* testName)) : fAllocator(allocator) , fCoincidence(nullptr) , fContourHead(head) , fNested(0) , fWindingFailed(false) , fPhase(SkOpPhase::kIntersecting) SkDEBUGPARAMS(fDebugTestName(testName)) SkDEBUGPARAMS(fAngleID(0)) SkDEBUGPARAMS(fCoinID(0)) SkDEBUGPARAMS(fContourID(0)) SkDEBUGPARAMS(fPtTID(0)) SkDEBUGPARAMS(fSegmentID(0)) SkDEBUGPARAMS(fSpanID(0)) SkDEBUGPARAMS(fDebugSkipAssert(debugSkipAssert)) { #if DEBUG_T_SECT_LOOP_COUNT debugResetLoopCounts(); #endif #if DEBUG_COIN fPreviousFuncName = nullptr; #endif } // given a prospective edge, compute its initial winding by projecting a ray // if the ray hits another edge // if the edge doesn't have a winding yet, hop up to that edge and start over // concern : check for hops forming a loop // if the edge is unsortable, or // the intersection is nearly at the ends, or // the tangent at the intersection is nearly coincident to the ray, // choose a different ray and try again // concern : if it is unable to succeed after N tries, try another edge? direction? // if no edge is hit, compute the winding directly // given the top span, project the most perpendicular ray and look for intersections // let's try up and then down. What the hey // bestXY is initialized by caller with basePt using namespace skia_private; enum class SkOpRayDir { kLeft, kTop, kRight, kBottom, }; #if DEBUG_WINDING const char* gDebugRayDirName[] = { "kLeft", "kTop", "kRight", "kBottom" }; #endif static int xy_index(SkOpRayDir dir) { return static_cast(dir) & 1; } static SkScalar pt_xy(const SkPoint& pt, SkOpRayDir dir) { return (&pt.fX)[xy_index(dir)]; } static SkScalar pt_yx(const SkPoint& pt, SkOpRayDir dir) { return (&pt.fX)[!xy_index(dir)]; } static double pt_dxdy(const SkDVector& v, SkOpRayDir dir) { return (&v.fX)[xy_index(dir)]; } static double pt_dydx(const SkDVector& v, SkOpRayDir dir) { return (&v.fX)[!xy_index(dir)]; } static SkScalar rect_side(const SkRect& r, SkOpRayDir dir) { return (&r.fLeft)[static_cast(dir)]; } static bool sideways_overlap(const SkRect& rect, const SkPoint& pt, SkOpRayDir dir) { int i = !xy_index(dir); return approximately_between((&rect.fLeft)[i], (&pt.fX)[i], (&rect.fRight)[i]); } static bool less_than(SkOpRayDir dir) { return static_cast((static_cast(dir) & 2) == 0); } static bool ccw_dxdy(const SkDVector& v, SkOpRayDir dir) { bool vPartPos = pt_dydx(v, dir) > 0; bool leftBottom = ((static_cast(dir) + 1) & 2) != 0; return vPartPos == leftBottom; } struct SkOpRayHit { SkOpRayDir makeTestBase(SkOpSpan* span, double t) { fNext = nullptr; fSpan = span; fT = span->t() * (1 - t) + span->next()->t() * t; SkOpSegment* segment = span->segment(); fSlope = segment->dSlopeAtT(fT); fPt = segment->ptAtT(fT); fValid = true; return fabs(fSlope.fX) < fabs(fSlope.fY) ? SkOpRayDir::kLeft : SkOpRayDir::kTop; } SkOpRayHit* fNext; SkOpSpan* fSpan; SkPoint fPt; double fT; SkDVector fSlope; bool fValid; }; void SkOpContour::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkArenaAlloc* allocator) { // if the bounds extreme is outside the best, we're done SkScalar baseXY = pt_xy(base.fPt, dir); SkScalar boundsXY = rect_side(fBounds, dir); bool checkLessThan = less_than(dir); if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { return; } SkOpSegment* testSegment = &fHead; do { testSegment->rayCheck(base, dir, hits, allocator); } while ((testSegment = testSegment->next())); } void SkOpSegment::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkArenaAlloc* allocator) { if (!sideways_overlap(fBounds, base.fPt, dir)) { return; } SkScalar baseXY = pt_xy(base.fPt, dir); SkScalar boundsXY = rect_side(fBounds, dir); bool checkLessThan = less_than(dir); if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { return; } double tVals[3]; SkScalar baseYX = pt_yx(base.fPt, dir); int roots = (*CurveIntercept[fVerb * 2 + xy_index(dir)])(fPts, fWeight, baseYX, tVals); for (int index = 0; index < roots; ++index) { double t = tVals[index]; if (base.fSpan->segment() == this && approximately_equal(base.fT, t)) { continue; } SkDVector slope; SkPoint pt; SkDEBUGCODE(sk_bzero(&slope, sizeof(slope))); bool valid = false; if (approximately_zero(t)) { pt = fPts[0]; } else if (approximately_equal(t, 1)) { pt = fPts[SkPathOpsVerbToPoints(fVerb)]; } else { SkASSERT(between(0, t, 1)); pt = this->ptAtT(t); if (SkDPoint::ApproximatelyEqual(pt, base.fPt)) { if (base.fSpan->segment() == this) { continue; } } else { SkScalar ptXY = pt_xy(pt, dir); if (!approximately_equal(baseXY, ptXY) && (baseXY < ptXY) == checkLessThan) { continue; } slope = this->dSlopeAtT(t); if (fVerb == SkPath::kCubic_Verb && base.fSpan->segment() == this && roughly_equal(base.fT, t) && SkDPoint::RoughlyEqual(pt, base.fPt)) { #if DEBUG_WINDING SkDebugf("%s (rarely expect this)\n", __FUNCTION__); #endif continue; } if (fabs(pt_dydx(slope, dir) * 10000) > fabs(pt_dxdy(slope, dir))) { valid = true; } } } SkOpSpan* span = this->windingSpanAtT(t); if (!span) { valid = false; } else if (!span->windValue() && !span->oppValue()) { continue; } SkOpRayHit* newHit = allocator->make(); newHit->fNext = *hits; newHit->fPt = pt; newHit->fSlope = slope; newHit->fSpan = span; newHit->fT = t; newHit->fValid = valid; *hits = newHit; } } SkOpSpan* SkOpSegment::windingSpanAtT(double tHit) { SkOpSpan* span = &fHead; SkOpSpanBase* next; do { next = span->next(); if (approximately_equal(tHit, next->t())) { return nullptr; } if (tHit < next->t()) { return span; } } while (!next->final() && (span = next->upCast())); return nullptr; } static bool hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { return a->fPt.fX < b->fPt.fX; } static bool reverse_hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { return b->fPt.fX < a->fPt.fX; } static bool hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { return a->fPt.fY < b->fPt.fY; } static bool reverse_hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { return b->fPt.fY < a->fPt.fY; } static double get_t_guess(int tTry, int* dirOffset) { double t = 0.5; *dirOffset = tTry & 1; int tBase = tTry >> 1; int tBits = 0; while (tTry >>= 1) { t /= 2; ++tBits; } if (tBits) { int tIndex = (tBase - 1) & ((1 << tBits) - 1); t += t * 2 * tIndex; } return t; } bool SkOpSpan::sortableTop(SkOpContour* contourHead) { SkSTArenaAlloc<1024> allocator; int dirOffset; double t = get_t_guess(fTopTTry++, &dirOffset); SkOpRayHit hitBase; SkOpRayDir dir = hitBase.makeTestBase(this, t); if (hitBase.fSlope.fX == 0 && hitBase.fSlope.fY == 0) { return false; } SkOpRayHit* hitHead = &hitBase; dir = static_cast(static_cast(dir) + dirOffset); if (hitBase.fSpan && hitBase.fSpan->segment()->verb() > SkPath::kLine_Verb && !pt_dydx(hitBase.fSlope, dir)) { return false; } SkOpContour* contour = contourHead; do { if (!contour->count()) { continue; } contour->rayCheck(hitBase, dir, &hitHead, &allocator); } while ((contour = contour->next())); // sort hits STArray<1, SkOpRayHit*> sorted; SkOpRayHit* hit = hitHead; while (hit) { sorted.push_back(hit); hit = hit->fNext; } int count = sorted.size(); SkTQSort(sorted.begin(), sorted.end(), xy_index(dir) ? less_than(dir) ? hit_compare_y : reverse_hit_compare_y : less_than(dir) ? hit_compare_x : reverse_hit_compare_x); // verify windings #if DEBUG_WINDING SkDebugf("%s dir=%s seg=%d t=%1.9g pt=(%1.9g,%1.9g)\n", __FUNCTION__, gDebugRayDirName[static_cast(dir)], hitBase.fSpan->segment()->debugID(), hitBase.fT, hitBase.fPt.fX, hitBase.fPt.fY); for (int index = 0; index < count; ++index) { hit = sorted[index]; SkOpSpan* span = hit->fSpan; SkOpSegment* hitSegment = span ? span->segment() : nullptr; bool operand = span ? hitSegment->operand() : false; bool ccw = ccw_dxdy(hit->fSlope, dir); SkDebugf("%s [%d] valid=%d operand=%d span=%d ccw=%d ", __FUNCTION__, index, hit->fValid, operand, span ? span->debugID() : -1, ccw); if (span) { hitSegment->dumpPtsInner(); } SkDebugf(" t=%1.9g pt=(%1.9g,%1.9g) slope=(%1.9g,%1.9g)\n", hit->fT, hit->fPt.fX, hit->fPt.fY, hit->fSlope.fX, hit->fSlope.fY); } #endif const SkPoint* last = nullptr; int wind = 0; int oppWind = 0; for (int index = 0; index < count; ++index) { hit = sorted[index]; if (!hit->fValid) { return false; } bool ccw = ccw_dxdy(hit->fSlope, dir); // SkASSERT(!approximately_zero(hit->fT) || !hit->fValid); SkOpSpan* span = hit->fSpan; if (!span) { return false; } SkOpSegment* hitSegment = span->segment(); if (span->windValue() == 0 && span->oppValue() == 0) { continue; } if (last && SkDPoint::ApproximatelyEqual(*last, hit->fPt)) { return false; } if (index < count - 1) { const SkPoint& next = sorted[index + 1]->fPt; if (SkDPoint::ApproximatelyEqual(next, hit->fPt)) { return false; } } bool operand = hitSegment->operand(); if (operand) { using std::swap; swap(wind, oppWind); } int lastWind = wind; int lastOpp = oppWind; int windValue = ccw ? -span->windValue() : span->windValue(); int oppValue = ccw ? -span->oppValue() : span->oppValue(); wind += windValue; oppWind += oppValue; bool sumSet = false; int spanSum = span->windSum(); int windSum = SkOpSegment::UseInnerWinding(lastWind, wind) ? wind : lastWind; if (spanSum == SK_MinS32) { span->setWindSum(windSum); sumSet = true; } else { // the need for this condition suggests that UseInnerWinding is flawed // happened when last = 1 wind = -1 #if 0 SkASSERT((hitSegment->isXor() ? (windSum & 1) == (spanSum & 1) : windSum == spanSum) || (abs(wind) == abs(lastWind) && (windSum ^ wind ^ lastWind) == spanSum)); #endif } int oSpanSum = span->oppSum(); int oppSum = SkOpSegment::UseInnerWinding(lastOpp, oppWind) ? oppWind : lastOpp; if (oSpanSum == SK_MinS32) { span->setOppSum(oppSum); } else { #if 0 SkASSERT(hitSegment->oppXor() ? (oppSum & 1) == (oSpanSum & 1) : oppSum == oSpanSum || (abs(oppWind) == abs(lastOpp) && (oppSum ^ oppWind ^ lastOpp) == oSpanSum)); #endif } if (sumSet) { if (this->globalState()->phase() == SkOpPhase::kFixWinding) { hitSegment->contour()->setCcw(ccw); } else { (void) hitSegment->markAndChaseWinding(span, span->next(), windSum, oppSum, nullptr); (void) hitSegment->markAndChaseWinding(span->next(), span, windSum, oppSum, nullptr); } } if (operand) { using std::swap; swap(wind, oppWind); } last = &hit->fPt; this->globalState()->bumpNested(); } return true; } SkOpSpan* SkOpSegment::findSortableTop(SkOpContour* contourHead) { SkOpSpan* span = &fHead; SkOpSpanBase* next; do { next = span->next(); if (span->done()) { continue; } if (span->windSum() != SK_MinS32) { return span; } if (span->sortableTop(contourHead)) { return span; } } while (!next->final() && (span = next->upCast())); return nullptr; } SkOpSpan* SkOpContour::findSortableTop(SkOpContour* contourHead) { bool allDone = true; if (fCount) { SkOpSegment* testSegment = &fHead; do { if (testSegment->done()) { continue; } allDone = false; SkOpSpan* result = testSegment->findSortableTop(contourHead); if (result) { return result; } } while ((testSegment = testSegment->next())); } if (allDone) { fDone = true; } return nullptr; } SkOpSpan* FindSortableTop(SkOpContourHead* contourHead) { for (int index = 0; index < SkOpGlobalState::kMaxWindingTries; ++index) { SkOpContour* contour = contourHead; do { if (contour->done()) { continue; } SkOpSpan* result = contour->findSortableTop(contourHead); if (result) { return result; } } while ((contour = contour->next())); } return nullptr; } using namespace skia_private; // wrap path to keep track of whether the contour is initialized and non-empty SkPathWriter::SkPathWriter(SkPath& path) : fPathPtr(&path) { init(); } void SkPathWriter::close() { if (fCurrent.isEmpty()) { return; } SkASSERT(this->isClosed()); #if DEBUG_PATH_CONSTRUCTION SkDebugf("path.close();\n"); #endif fCurrent.close(); fPathPtr->addPath(fCurrent); fCurrent.reset(); init(); } void SkPathWriter::conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight) { SkPoint pt2pt = this->update(pt2); #if DEBUG_PATH_CONSTRUCTION SkDebugf("path.conicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g);\n", pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY, weight); #endif fCurrent.conicTo(pt1, pt2pt, weight); } void SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3) { SkPoint pt3pt = this->update(pt3); #if DEBUG_PATH_CONSTRUCTION SkDebugf("path.cubicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g,%1.9g);\n", pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3pt.fX, pt3pt.fY); #endif fCurrent.cubicTo(pt1, pt2, pt3pt); } bool SkPathWriter::deferredLine(const SkOpPtT* pt) { SkASSERT(fFirstPtT); SkASSERT(fDefer[0]); if (fDefer[0] == pt) { // FIXME: why we're adding a degenerate line? Caller should have preflighted this. return true; } if (pt->contains(fDefer[0])) { // FIXME: why we're adding a degenerate line? return true; } if (this->matchedLast(pt)) { return false; } if (fDefer[1] && this->changedSlopes(pt)) { this->lineTo(); fDefer[0] = fDefer[1]; } fDefer[1] = pt; return true; } void SkPathWriter::deferredMove(const SkOpPtT* pt) { if (!fDefer[1]) { fFirstPtT = fDefer[0] = pt; return; } SkASSERT(fDefer[0]); if (!this->matchedLast(pt)) { this->finishContour(); fFirstPtT = fDefer[0] = pt; } } void SkPathWriter::finishContour() { if (!this->matchedLast(fDefer[0])) { if (!fDefer[1]) { return; } this->lineTo(); } if (fCurrent.isEmpty()) { return; } if (this->isClosed()) { this->close(); } else { SkASSERT(fDefer[1]); fEndPtTs.push_back(fFirstPtT); fEndPtTs.push_back(fDefer[1]); fPartials.push_back(fCurrent); this->init(); } } void SkPathWriter::init() { fCurrent.reset(); fFirstPtT = fDefer[0] = fDefer[1] = nullptr; } bool SkPathWriter::isClosed() const { return this->matchedLast(fFirstPtT); } void SkPathWriter::lineTo() { if (fCurrent.isEmpty()) { this->moveTo(); } #if DEBUG_PATH_CONSTRUCTION SkDebugf("path.lineTo(%1.9g,%1.9g);\n", fDefer[1]->fPt.fX, fDefer[1]->fPt.fY); #endif fCurrent.lineTo(fDefer[1]->fPt); } bool SkPathWriter::matchedLast(const SkOpPtT* test) const { if (test == fDefer[1]) { return true; } if (!test) { return false; } if (!fDefer[1]) { return false; } return test->contains(fDefer[1]); } void SkPathWriter::moveTo() { #if DEBUG_PATH_CONSTRUCTION SkDebugf("path.moveTo(%1.9g,%1.9g);\n", fFirstPtT->fPt.fX, fFirstPtT->fPt.fY); #endif fCurrent.moveTo(fFirstPtT->fPt); } void SkPathWriter::quadTo(const SkPoint& pt1, const SkOpPtT* pt2) { SkPoint pt2pt = this->update(pt2); #if DEBUG_PATH_CONSTRUCTION SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n", pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY); #endif fCurrent.quadTo(pt1, pt2pt); } // if last point to be written matches the current path's first point, alter the // last to avoid writing a degenerate lineTo when the path is closed SkPoint SkPathWriter::update(const SkOpPtT* pt) { if (!fDefer[1]) { this->moveTo(); } else if (!this->matchedLast(fDefer[0])) { this->lineTo(); } SkPoint result = pt->fPt; if (fFirstPtT && result != fFirstPtT->fPt && fFirstPtT->contains(pt)) { result = fFirstPtT->fPt; } fDefer[0] = fDefer[1] = pt; // set both to know that there is not a pending deferred line return result; } bool SkPathWriter::someAssemblyRequired() { this->finishContour(); return !fEndPtTs.empty(); } bool SkPathWriter::changedSlopes(const SkOpPtT* ptT) const { if (matchedLast(fDefer[0])) { return false; } SkVector deferDxdy = fDefer[1]->fPt - fDefer[0]->fPt; SkVector lineDxdy = ptT->fPt - fDefer[1]->fPt; return deferDxdy.fX * lineDxdy.fY != deferDxdy.fY * lineDxdy.fX; } class DistanceLessThan { public: DistanceLessThan(double* distances) : fDistances(distances) { } double* fDistances; bool operator()(const int one, const int two) const { return fDistances[one] < fDistances[two]; } }; /* check start and end of each contour if not the same, record them match them up connect closest reassemble contour pieces into new path */ void SkPathWriter::assemble() { if (!this->someAssemblyRequired()) { return; } #if DEBUG_PATH_CONSTRUCTION SkDebugf("%s\n", __FUNCTION__); #endif SkOpPtT const* const* runs = fEndPtTs.begin(); // starts, ends of partial contours int endCount = fEndPtTs.size(); // all starts and ends SkASSERT(endCount > 0); SkASSERT(endCount == fPartials.size() * 2); #if DEBUG_ASSEMBLE for (int index = 0; index < endCount; index += 2) { const SkOpPtT* eStart = runs[index]; const SkOpPtT* eEnd = runs[index + 1]; SkASSERT(eStart != eEnd); SkASSERT(!eStart->contains(eEnd)); SkDebugf("%s contour start=(%1.9g,%1.9g) end=(%1.9g,%1.9g)\n", __FUNCTION__, eStart->fPt.fX, eStart->fPt.fY, eEnd->fPt.fX, eEnd->fPt.fY); } #endif // lengthen any partial contour adjacent to a simple segment for (int pIndex = 0; pIndex < endCount; pIndex++) { SkOpPtT* opPtT = const_cast(runs[pIndex]); SkPath p; SkPathWriter partWriter(p); do { if (!zero_or_one(opPtT->fT)) { break; } SkOpSpanBase* opSpanBase = opPtT->span(); SkOpSpanBase* start = opPtT->fT ? opSpanBase->prev() : opSpanBase->upCast()->next(); int step = opPtT->fT ? 1 : -1; const SkOpSegment* opSegment = opSpanBase->segment(); const SkOpSegment* nextSegment = opSegment->isSimple(&start, &step); if (!nextSegment) { break; } SkOpSpanBase* opSpanEnd = start->t() ? start->prev() : start->upCast()->next(); if (start->starter(opSpanEnd)->alreadyAdded()) { break; } nextSegment->addCurveTo(start, opSpanEnd, &partWriter); opPtT = opSpanEnd->ptT(); SkOpPtT** runsPtr = const_cast(&runs[pIndex]); *runsPtr = opPtT; } while (true); partWriter.finishContour(); const TArray& partPartials = partWriter.partials(); if (partPartials.empty()) { continue; } // if pIndex is even, reverse and prepend to fPartials; otherwise, append SkPath& partial = const_cast(fPartials[pIndex >> 1]); const SkPath& part = partPartials[0]; if (pIndex & 1) { partial.addPath(part, SkPath::kExtend_AddPathMode); } else { SkPath reverse; reverse.reverseAddPath(part); reverse.addPath(partial, SkPath::kExtend_AddPathMode); partial = reverse; } } SkTDArray sLink, eLink; int linkCount = endCount / 2; // number of partial contours sLink.append(linkCount); eLink.append(linkCount); int rIndex, iIndex; for (rIndex = 0; rIndex < linkCount; ++rIndex) { sLink[rIndex] = eLink[rIndex] = SK_MaxS32; } const int entries = endCount * (endCount - 1) / 2; // folded triangle STArray<8, double, true> distances(entries); STArray<8, int, true> sortedDist(entries); STArray<8, int, true> distLookup(entries); int rRow = 0; int dIndex = 0; for (rIndex = 0; rIndex < endCount - 1; ++rIndex) { const SkOpPtT* oPtT = runs[rIndex]; for (iIndex = rIndex + 1; iIndex < endCount; ++iIndex) { const SkOpPtT* iPtT = runs[iIndex]; double dx = iPtT->fPt.fX - oPtT->fPt.fX; double dy = iPtT->fPt.fY - oPtT->fPt.fY; double dist = dx * dx + dy * dy; distLookup.push_back(rRow + iIndex); distances.push_back(dist); // oStart distance from iStart sortedDist.push_back(dIndex++); } rRow += endCount; } SkASSERT(dIndex == entries); SkTQSort(sortedDist.begin(), sortedDist.end(), DistanceLessThan(distances.begin())); int remaining = linkCount; // number of start/end pairs for (rIndex = 0; rIndex < entries; ++rIndex) { int pair = sortedDist[rIndex]; pair = distLookup[pair]; int row = pair / endCount; int col = pair - row * endCount; int ndxOne = row >> 1; bool endOne = row & 1; int* linkOne = endOne ? eLink.begin() : sLink.begin(); if (linkOne[ndxOne] != SK_MaxS32) { continue; } int ndxTwo = col >> 1; bool endTwo = col & 1; int* linkTwo = endTwo ? eLink.begin() : sLink.begin(); if (linkTwo[ndxTwo] != SK_MaxS32) { continue; } SkASSERT(&linkOne[ndxOne] != &linkTwo[ndxTwo]); bool flip = endOne == endTwo; linkOne[ndxOne] = flip ? ~ndxTwo : ndxTwo; linkTwo[ndxTwo] = flip ? ~ndxOne : ndxOne; if (!--remaining) { break; } } SkASSERT(!remaining); #if DEBUG_ASSEMBLE for (rIndex = 0; rIndex < linkCount; ++rIndex) { int s = sLink[rIndex]; int e = eLink[rIndex]; SkDebugf("%s %c%d <- s%d - e%d -> %c%d\n", __FUNCTION__, s < 0 ? 's' : 'e', s < 0 ? ~s : s, rIndex, rIndex, e < 0 ? 'e' : 's', e < 0 ? ~e : e); } #endif rIndex = 0; do { bool forward = true; bool first = true; int sIndex = sLink[rIndex]; SkASSERT(sIndex != SK_MaxS32); sLink[rIndex] = SK_MaxS32; int eIndex; if (sIndex < 0) { eIndex = sLink[~sIndex]; sLink[~sIndex] = SK_MaxS32; } else { eIndex = eLink[sIndex]; eLink[sIndex] = SK_MaxS32; } SkASSERT(eIndex != SK_MaxS32); #if DEBUG_ASSEMBLE SkDebugf("%s sIndex=%c%d eIndex=%c%d\n", __FUNCTION__, sIndex < 0 ? 's' : 'e', sIndex < 0 ? ~sIndex : sIndex, eIndex < 0 ? 's' : 'e', eIndex < 0 ? ~eIndex : eIndex); #endif do { const SkPath& contour = fPartials[rIndex]; if (!first) { SkPoint prior, next; if (!fPathPtr->getLastPt(&prior)) { return; } if (forward) { next = contour.getPoint(0); } else { SkAssertResult(contour.getLastPt(&next)); } if (prior != next) { /* TODO: if there is a gap between open path written so far and path to come, connect by following segments from one to the other, rather than introducing a diagonal to connect the two. */ } } if (forward) { fPathPtr->addPath(contour, first ? SkPath::kAppend_AddPathMode : SkPath::kExtend_AddPathMode); } else { SkASSERT(!first); fPathPtr->reversePathTo(contour); } if (first) { first = false; } #if DEBUG_ASSEMBLE SkDebugf("%s rIndex=%d eIndex=%s%d close=%d\n", __FUNCTION__, rIndex, eIndex < 0 ? "~" : "", eIndex < 0 ? ~eIndex : eIndex, sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex)); #endif if (sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex)) { fPathPtr->close(); break; } if (forward) { eIndex = eLink[rIndex]; SkASSERT(eIndex != SK_MaxS32); eLink[rIndex] = SK_MaxS32; if (eIndex >= 0) { SkASSERT(sLink[eIndex] == rIndex); sLink[eIndex] = SK_MaxS32; } else { SkASSERT(eLink[~eIndex] == ~rIndex); eLink[~eIndex] = SK_MaxS32; } } else { eIndex = sLink[rIndex]; SkASSERT(eIndex != SK_MaxS32); sLink[rIndex] = SK_MaxS32; if (eIndex >= 0) { SkASSERT(eLink[eIndex] == rIndex); eLink[eIndex] = SK_MaxS32; } else { SkASSERT(sLink[~eIndex] == ~rIndex); sLink[~eIndex] = SK_MaxS32; } } rIndex = eIndex; if (rIndex < 0) { forward ^= 1; rIndex = ~rIndex; } } while (true); for (rIndex = 0; rIndex < linkCount; ++rIndex) { if (sLink[rIndex] != SK_MaxS32) { break; } } } while (rIndex < linkCount); #if DEBUG_ASSEMBLE for (rIndex = 0; rIndex < linkCount; ++rIndex) { SkASSERT(sLink[rIndex] == SK_MaxS32); SkASSERT(eLink[rIndex] == SK_MaxS32); } #endif return; } int SkReduceOrder::reduce(const SkDLine& line) { fLine[0] = line[0]; int different = line[0] != line[1]; fLine[1] = line[different]; return 1 + different; } static int coincident_line(const SkDQuad& quad, SkDQuad& reduction) { reduction[0] = reduction[1] = quad[0]; return 1; } static int reductionLineCount(const SkDQuad& reduction) { return 1 + !reduction[0].approximatelyEqual(reduction[1]); } static int vertical_line(const SkDQuad& quad, SkDQuad& reduction) { reduction[0] = quad[0]; reduction[1] = quad[2]; return reductionLineCount(reduction); } static int horizontal_line(const SkDQuad& quad, SkDQuad& reduction) { reduction[0] = quad[0]; reduction[1] = quad[2]; return reductionLineCount(reduction); } static int check_linear(const SkDQuad& quad, int minX, int maxX, int minY, int maxY, SkDQuad& reduction) { if (!quad.isLinear(0, 2)) { return 0; } // four are colinear: return line formed by outside reduction[0] = quad[0]; reduction[1] = quad[2]; return reductionLineCount(reduction); } // reduce to a quadratic or smaller // look for identical points // look for all four points in a line // note that three points in a line doesn't simplify a cubic // look for approximation with single quadratic // save approximation with multiple quadratics for later int SkReduceOrder::reduce(const SkDQuad& quad) { int index, minX, maxX, minY, maxY; int minXSet, minYSet; minX = maxX = minY = maxY = 0; minXSet = minYSet = 0; for (index = 1; index < 3; ++index) { if (quad[minX].fX > quad[index].fX) { minX = index; } if (quad[minY].fY > quad[index].fY) { minY = index; } if (quad[maxX].fX < quad[index].fX) { maxX = index; } if (quad[maxY].fY < quad[index].fY) { maxY = index; } } for (index = 0; index < 3; ++index) { if (AlmostEqualUlps(quad[index].fX, quad[minX].fX)) { minXSet |= 1 << index; } if (AlmostEqualUlps(quad[index].fY, quad[minY].fY)) { minYSet |= 1 << index; } } if ((minXSet & 0x05) == 0x5 && (minYSet & 0x05) == 0x5) { // test for degenerate // this quad starts and ends at the same place, so never contributes // to the fill return coincident_line(quad, fQuad); } if (minXSet == 0x7) { // test for vertical line return vertical_line(quad, fQuad); } if (minYSet == 0x7) { // test for horizontal line return horizontal_line(quad, fQuad); } int result = check_linear(quad, minX, maxX, minY, maxY, fQuad); if (result) { return result; } fQuad = quad; return 3; } //////////////////////////////////////////////////////////////////////////////////// static int coincident_line(const SkDCubic& cubic, SkDCubic& reduction) { reduction[0] = reduction[1] = cubic[0]; return 1; } static int reductionLineCount(const SkDCubic& reduction) { return 1 + !reduction[0].approximatelyEqual(reduction[1]); } static int vertical_line(const SkDCubic& cubic, SkDCubic& reduction) { reduction[0] = cubic[0]; reduction[1] = cubic[3]; return reductionLineCount(reduction); } static int horizontal_line(const SkDCubic& cubic, SkDCubic& reduction) { reduction[0] = cubic[0]; reduction[1] = cubic[3]; return reductionLineCount(reduction); } // check to see if it is a quadratic or a line static int check_quadratic(const SkDCubic& cubic, SkDCubic& reduction) { double dx10 = cubic[1].fX - cubic[0].fX; double dx23 = cubic[2].fX - cubic[3].fX; double midX = cubic[0].fX + dx10 * 3 / 2; double sideAx = midX - cubic[3].fX; double sideBx = dx23 * 3 / 2; if (approximately_zero(sideAx) ? !approximately_equal(sideAx, sideBx) : !AlmostEqualUlps_Pin(sideAx, sideBx)) { return 0; } double dy10 = cubic[1].fY - cubic[0].fY; double dy23 = cubic[2].fY - cubic[3].fY; double midY = cubic[0].fY + dy10 * 3 / 2; double sideAy = midY - cubic[3].fY; double sideBy = dy23 * 3 / 2; if (approximately_zero(sideAy) ? !approximately_equal(sideAy, sideBy) : !AlmostEqualUlps_Pin(sideAy, sideBy)) { return 0; } reduction[0] = cubic[0]; reduction[1].fX = midX; reduction[1].fY = midY; reduction[2] = cubic[3]; return 3; } static int check_linear(const SkDCubic& cubic, int minX, int maxX, int minY, int maxY, SkDCubic& reduction) { if (!cubic.isLinear(0, 3)) { return 0; } // four are colinear: return line formed by outside reduction[0] = cubic[0]; reduction[1] = cubic[3]; return reductionLineCount(reduction); } /* food for thought: http://objectmix.com/graphics/132906-fast-precision-driven-cubic-quadratic-piecewise-degree-reduction-algos-2-a.html Given points c1, c2, c3 and c4 of a cubic Bezier, the points of the corresponding quadratic Bezier are (given in convex combinations of points): q1 = (11/13)c1 + (3/13)c2 -(3/13)c3 + (2/13)c4 q2 = -c1 + (3/2)c2 + (3/2)c3 - c4 q3 = (2/13)c1 - (3/13)c2 + (3/13)c3 + (11/13)c4 Of course, this curve does not interpolate the end-points, but it would be interesting to see the behaviour of such a curve in an applet. -- Kalle Rutanen http://kaba.hilvi.org */ // reduce to a quadratic or smaller // look for identical points // look for all four points in a line // note that three points in a line doesn't simplify a cubic // look for approximation with single quadratic // save approximation with multiple quadratics for later int SkReduceOrder::reduce(const SkDCubic& cubic, Quadratics allowQuadratics) { int index, minX, maxX, minY, maxY; int minXSet, minYSet; minX = maxX = minY = maxY = 0; minXSet = minYSet = 0; for (index = 1; index < 4; ++index) { if (cubic[minX].fX > cubic[index].fX) { minX = index; } if (cubic[minY].fY > cubic[index].fY) { minY = index; } if (cubic[maxX].fX < cubic[index].fX) { maxX = index; } if (cubic[maxY].fY < cubic[index].fY) { maxY = index; } } for (index = 0; index < 4; ++index) { double cx = cubic[index].fX; double cy = cubic[index].fY; double denom = std::max(fabs(cx), std::max(fabs(cy), std::max(fabs(cubic[minX].fX), fabs(cubic[minY].fY)))); if (denom == 0) { minXSet |= 1 << index; minYSet |= 1 << index; continue; } double inv = 1 / denom; if (approximately_equal_half(cx * inv, cubic[minX].fX * inv)) { minXSet |= 1 << index; } if (approximately_equal_half(cy * inv, cubic[minY].fY * inv)) { minYSet |= 1 << index; } } if (minXSet == 0xF) { // test for vertical line if (minYSet == 0xF) { // return 1 if all four are coincident return coincident_line(cubic, fCubic); } return vertical_line(cubic, fCubic); } if (minYSet == 0xF) { // test for horizontal line return horizontal_line(cubic, fCubic); } int result = check_linear(cubic, minX, maxX, minY, maxY, fCubic); if (result) { return result; } if (allowQuadratics == SkReduceOrder::kAllow_Quadratics && (result = check_quadratic(cubic, fCubic))) { return result; } fCubic = cubic; return 4; } SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkPoint* reducePts) { SkDQuad quad; quad.set(a); SkReduceOrder reducer; int order = reducer.reduce(quad); if (order == 2) { // quad became line for (int index = 0; index < order; ++index) { *reducePts++ = reducer.fLine[index].asSkPoint(); } } return SkPathOpsPointsToVerb(order - 1); } SkPath::Verb SkReduceOrder::Conic(const SkConic& c, SkPoint* reducePts) { SkPath::Verb verb = SkReduceOrder::Quad(c.fPts, reducePts); if (verb > SkPath::kLine_Verb && c.fW == 1) { return SkPath::kQuad_Verb; } return verb == SkPath::kQuad_Verb ? SkPath::kConic_Verb : verb; } SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) { if (SkDPoint::ApproximatelyEqual(a[0], a[1]) && SkDPoint::ApproximatelyEqual(a[0], a[2]) && SkDPoint::ApproximatelyEqual(a[0], a[3])) { reducePts[0] = a[0]; return SkPath::kMove_Verb; } SkDCubic cubic; cubic.set(a); SkReduceOrder reducer; int order = reducer.reduce(cubic, kAllow_Quadratics); if (order == 2 || order == 3) { // cubic became line or quad for (int index = 0; index < order; ++index) { *reducePts++ = reducer.fQuad[index].asSkPoint(); } } return SkPathOpsPointsToVerb(order - 1); } #if defined(SK_DEBUG) && defined(SK_BUILD_FOR_WIN) // This is a super stable value and setting it here avoids pulling in all of windows.h. #ifndef FAST_FAIL_FATAL_APP_EXIT #define FAST_FAIL_FATAL_APP_EXIT 7 #endif #endif #ifdef SK_BUILD_FOR_ANDROID_FRAMEWORK #define SK_DEBUGFAILF(fmt, ...) SK_ABORT(fmt"\n", __VA_ARGS__) #else #define SK_DEBUGFAILF(fmt, ...) SkASSERT((SkDebugf(fmt"\n", __VA_ARGS__), false)) #endif static inline void sk_out_of_memory(size_t size) { SK_DEBUGFAILF("sk_out_of_memory (asked for %zu bytes)", size); #if defined(SK_BUILD_FOR_AFL_FUZZ) exit(1); #else abort(); #endif } static inline void* throw_on_failure(size_t size, void* p) { if (size > 0 && p == nullptr) { // If we've got a nullptr here, the only reason we should have failed is running out of RAM. sk_out_of_memory(size); } return p; } void sk_abort_no_print() { #if defined(SK_DEBUG) && defined(SK_BUILD_FOR_WIN) __fastfail(FAST_FAIL_FATAL_APP_EXIT); #elif defined(__clang__) __builtin_trap(); #else abort(); #endif } void sk_out_of_memory(void) { SkDEBUGFAIL("sk_out_of_memory"); #if defined(SK_BUILD_FOR_AFL_FUZZ) exit(1); #else abort(); #endif } void* sk_realloc_throw(void* addr, size_t size) { if (size == 0) { sk_free(addr); return nullptr; } return throw_on_failure(size, realloc(addr, size)); } void sk_free(void* p) { // The guard here produces a performance improvement across many tests, and many platforms. // Removing the check was tried in skia cl 588037. if (p != nullptr) { free(p); } } void* sk_malloc_flags(size_t size, unsigned flags) { void* p; if (flags & SK_MALLOC_ZERO_INITIALIZE) { p = calloc(size, 1); } else { #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) && defined(__BIONIC__) /* TODO: After b/169449588 is fixed, we will want to change this to restore * original behavior instead of always disabling the flag. * TODO: After b/158870657 is fixed and scudo is used globally, we can assert when an * an error is returned. */ // malloc() generally doesn't initialize its memory and that's a huge security hole, // so Android has replaced its malloc() with one that zeros memory, // but that's a huge performance hit for HWUI, so turn it back off again. (void)mallopt(M_THREAD_DISABLE_MEM_INIT, 1); #endif p = malloc(size); #if defined(SK_BUILD_FOR_ANDROID_FRAMEWORK) && defined(__BIONIC__) (void)mallopt(M_THREAD_DISABLE_MEM_INIT, 0); #endif } if (flags & SK_MALLOC_THROW) { return throw_on_failure(size, p); } else { return p; } } static inline bool is_between(int c, int min, int max) { return (unsigned)(c - min) <= (unsigned)(max - min); } static inline bool is_ws(int c) { return is_between(c, 1, 32); } static inline bool is_digit(int c) { return is_between(c, '0', '9'); } static inline bool is_sep(int c) { return is_ws(c) || c == ',' || c == ';'; } static int to_hex(int c) { if (is_digit(c)) return c - '0'; c |= 0x20; // make us lower-case if (is_between(c, 'a', 'f')) return c + 10 - 'a'; else return -1; } static inline bool is_hex(int c) { return to_hex(c) >= 0; } static const char* skip_ws(const char str[]) { SkASSERT(str); while (is_ws(*str)) str++; return str; } static const char* skip_sep(const char str[]) { SkASSERT(str); while (is_sep(*str)) str++; return str; } int SkParse::Count(const char str[]) { char c; int count = 0; goto skipLeading; do { count++; do { if ((c = *str++) == '\0') goto goHome; } while (is_sep(c) == false); skipLeading: do { if ((c = *str++) == '\0') goto goHome; } while (is_sep(c)); } while (true); goHome: return count; } int SkParse::Count(const char str[], char separator) { char c; int count = 0; goto skipLeading; do { count++; do { if ((c = *str++) == '\0') goto goHome; } while (c != separator); skipLeading: do { if ((c = *str++) == '\0') goto goHome; } while (c == separator); } while (true); goHome: return count; } const char* SkParse::FindHex(const char str[], uint32_t* value) { SkASSERT(str); str = skip_ws(str); if (!is_hex(*str)) return nullptr; uint32_t n = 0; int max_digits = 8; int digit; while ((digit = to_hex(*str)) >= 0) { if (--max_digits < 0) return nullptr; n = (n << 4) | digit; str += 1; } if (*str == 0 || is_ws(*str)) { if (value) *value = n; return str; } return nullptr; } const char* SkParse::FindS32(const char str[], int32_t* value) { SkASSERT(str); str = skip_ws(str); int sign = 1; int64_t maxAbsValue = std::numeric_limits::max(); if (*str == '-') { sign = -1; maxAbsValue = -static_cast(std::numeric_limits::min()); str += 1; } if (!is_digit(*str)) { return nullptr; } int64_t n = 0; while (is_digit(*str)) { n = 10*n + *str - '0'; if (n > maxAbsValue) { return nullptr; } str += 1; } if (value) { *value = SkToS32(sign*n); } return str; } const char* SkParse::FindMSec(const char str[], SkMSec* value) { SkASSERT(str); str = skip_ws(str); int sign = 0; if (*str == '-') { sign = -1; str += 1; } if (!is_digit(*str)) return nullptr; int n = 0; while (is_digit(*str)) { n = 10*n + *str - '0'; str += 1; } int remaining10s = 3; if (*str == '.') { str++; while (is_digit(*str)) { n = 10*n + *str - '0'; str += 1; if (--remaining10s == 0) break; } } while (--remaining10s >= 0) n *= 10; if (value) *value = (n ^ sign) - sign; return str; } const char* SkParse::FindScalar(const char str[], SkScalar* value) { SkASSERT(str); str = skip_ws(str); char* stop; float v = (float)strtod(str, &stop); if (str == stop) { return nullptr; } if (value) { *value = v; } return stop; } const char* SkParse::FindScalars(const char str[], SkScalar value[], int count) { SkASSERT(count >= 0); if (count > 0) { for (;;) { str = SkParse::FindScalar(str, value); if (--count == 0 || str == nullptr) break; // keep going str = skip_sep(str); if (value) value += 1; } } return str; } static bool lookup_str(const char str[], const char** table, int count) { while (--count >= 0) if (!strcmp(str, table[count])) return true; return false; } bool SkParse::FindBool(const char str[], bool* value) { static const char* gYes[] = { "yes", "1", "true" }; static const char* gNo[] = { "no", "0", "false" }; if (lookup_str(str, gYes, std::size(gYes))) { if (value) *value = true; return true; } else if (lookup_str(str, gNo, std::size(gNo))) { if (value) *value = false; return true; } return false; } int SkParse::FindList(const char target[], const char list[]) { size_t len = strlen(target); int index = 0; for (;;) { const char* end = strchr(list, ','); size_t entryLen; if (end == nullptr) // last entry entryLen = strlen(list); else entryLen = end - list; if (entryLen == len && memcmp(target, list, len) == 0) return index; if (end == nullptr) break; list = end + 1; // skip the ',' index += 1; } return -1; } enum class SkPathDirection; static inline bool skParsePath_is_between(int c, int min, int max) { return (unsigned)(c - min) <= (unsigned)(max - min); } static inline bool skParsePath_is_ws(int c) { return skParsePath_is_between(c, 1, 32); } static inline bool skParsePath_is_digit(int c) { return skParsePath_is_between(c, '0', '9'); } static inline bool skParsePath_is_sep(int c) { return skParsePath_is_ws(c) || c == ','; } static inline bool is_lower(int c) { return skParsePath_is_between(c, 'a', 'z'); } static inline int to_upper(int c) { return c - 'a' + 'A'; } static const char* skParsePath_skip_ws(const char str[]) { SkASSERT(str); while (skParsePath_is_ws(*str)) str++; return str; } static const char* skParsePath_skip_sep(const char str[]) { if (!str) { return nullptr; } while (skParsePath_is_sep(*str)) str++; return str; } static const char* find_points(const char str[], SkPoint value[], int count, bool isRelative, SkPoint* relative) { str = SkParse::FindScalars(str, &value[0].fX, count * 2); if (isRelative) { for (int index = 0; index < count; index++) { value[index].fX += relative->fX; value[index].fY += relative->fY; } } return str; } static const char* find_scalar(const char str[], SkScalar* value, bool isRelative, SkScalar relative) { str = SkParse::FindScalar(str, value); if (!str) { return nullptr; } if (isRelative) { *value += relative; } str = skParsePath_skip_sep(str); return str; } // https://www.w3.org/TR/SVG11/paths.html#PathDataBNF // // flag: // "0" | "1" static const char* find_flag(const char str[], bool* value) { if (!str) { return nullptr; } if (str[0] != '1' && str[0] != '0') { return nullptr; } *value = str[0] != '0'; str = skParsePath_skip_sep(str + 1); return str; } bool SkParsePath::FromSVGString(const char data[], SkPath* result) { SkPath path; SkPoint first = {0, 0}; SkPoint c = {0, 0}; SkPoint lastc = {0, 0}; SkPoint points[3]; char op = '\0'; char previousOp = '\0'; bool relative = false; for (;;) { if (!data) { // Truncated data return false; } data = skParsePath_skip_ws(data); if (data[0] == '\0') { break; } char ch = data[0]; if (skParsePath_is_digit(ch) || ch == '-' || ch == '+' || ch == '.') { if (op == '\0' || op == 'Z') { return false; } } else if (skParsePath_is_sep(ch)) { data = skParsePath_skip_sep(data); } else { op = ch; relative = false; if (is_lower(op)) { op = (char) to_upper(op); relative = true; } data++; data = skParsePath_skip_sep(data); } switch (op) { case 'M': data = find_points(data, points, 1, relative, &c); path.moveTo(points[0]); previousOp = '\0'; op = 'L'; c = points[0]; break; case 'L': data = find_points(data, points, 1, relative, &c); path.lineTo(points[0]); c = points[0]; break; case 'H': { SkScalar x; data = find_scalar(data, &x, relative, c.fX); path.lineTo(x, c.fY); c.fX = x; } break; case 'V': { SkScalar y; data = find_scalar(data, &y, relative, c.fY); path.lineTo(c.fX, y); c.fY = y; } break; case 'C': data = find_points(data, points, 3, relative, &c); goto cubicCommon; case 'S': data = find_points(data, &points[1], 2, relative, &c); points[0] = c; if (previousOp == 'C' || previousOp == 'S') { points[0].fX -= lastc.fX - c.fX; points[0].fY -= lastc.fY - c.fY; } cubicCommon: path.cubicTo(points[0], points[1], points[2]); lastc = points[1]; c = points[2]; break; case 'Q': // Quadratic Bezier Curve data = find_points(data, points, 2, relative, &c); goto quadraticCommon; case 'T': data = find_points(data, &points[1], 1, relative, &c); points[0] = c; if (previousOp == 'Q' || previousOp == 'T') { points[0].fX -= lastc.fX - c.fX; points[0].fY -= lastc.fY - c.fY; } quadraticCommon: path.quadTo(points[0], points[1]); lastc = points[0]; c = points[1]; break; case 'A': { SkPoint radii; SkScalar angle; bool largeArc, sweep; if ((data = find_points(data, &radii, 1, false, nullptr)) && (data = skParsePath_skip_sep(data)) && (data = find_scalar(data, &angle, false, 0)) && (data = skParsePath_skip_sep(data)) && (data = find_flag(data, &largeArc)) && (data = skParsePath_skip_sep(data)) && (data = find_flag(data, &sweep)) && (data = skParsePath_skip_sep(data)) && (data = find_points(data, &points[0], 1, relative, &c))) { path.arcTo(radii, angle, (SkPath::ArcSize) largeArc, (SkPathDirection) !sweep, points[0]); path.getLastPt(&c); } } break; case 'Z': path.close(); c = first; break; case '~': { SkPoint args[2]; data = find_points(data, args, 2, false, nullptr); path.moveTo(args[0].fX, args[0].fY); path.lineTo(args[1].fX, args[1].fY); } break; default: return false; } if (previousOp == 0) { first = c; } previousOp = op; } // we're good, go ahead and swap in the result result->swap(path); return true; } /////////////////////////////////////////////////////////////////////////////// static void write_scalar(SkWStream* stream, SkScalar value) { char buffer[64]; int len = snprintf(buffer, sizeof(buffer), "%g", value); char* stop = buffer + len; stream->write(buffer, stop - buffer); } SkString SkParsePath::ToSVGString(const SkPath& path, PathEncoding encoding) { SkDynamicMemoryWStream stream; SkPoint current_point{0,0}; const auto rel_selector = encoding == PathEncoding::Relative; const auto append_command = [&](char cmd, const SkPoint pts[], size_t count) { // Use lower case cmds for relative encoding. cmd += 32 * rel_selector; stream.write(&cmd, 1); for (size_t i = 0; i < count; ++i) { const auto pt = pts[i] - current_point; if (i > 0) { stream.write(" ", 1); } write_scalar(&stream, pt.fX); stream.write(" ", 1); write_scalar(&stream, pt.fY); } SkASSERT(count > 0); // For relative encoding, track the current point (otherwise == origin). current_point = pts[count - 1] * rel_selector; }; SkPath::Iter iter(path, false); SkPoint pts[4]; for (;;) { switch (iter.next(pts)) { case SkPath::kConic_Verb: { const SkScalar tol = SK_Scalar1 / 1024; // how close to a quad SkAutoConicToQuads quadder; const SkPoint* quadPts = quadder.computeQuads(pts, iter.conicWeight(), tol); for (int i = 0; i < quadder.countQuads(); ++i) { append_command('Q', &quadPts[i*2 + 1], 2); } } break; case SkPath::kMove_Verb: append_command('M', &pts[0], 1); break; case SkPath::kLine_Verb: append_command('L', &pts[1], 1); break; case SkPath::kQuad_Verb: append_command('Q', &pts[1], 2); break; case SkPath::kCubic_Verb: append_command('C', &pts[1], 3); break; case SkPath::kClose_Verb: stream.write("Z", 1); break; case SkPath::kDone_Verb: { SkString str; str.resize(stream.bytesWritten()); stream.copyTo(str.data()); return str; } } } } #ifdef SKIA_SIMPLIFY_NAMESPACE } // namespace SKIA_SIMPLIFY_NAMESPACE #endif #undef TArray #undef Top #undef ERROR #if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic pop #elif defined(_MSC_VER) #pragma warning(pop) #endif