Files
UnrealEngine/Engine/Source/ThirdParty/skia/skia-simplify.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

54184 lines
1.8 MiB

/*
* 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 <array>
#include <cassert>
#include <cfloat>
#include <cstdarg>
#include <cstdlib>
#include <cstdio>
#include <optional>
#include <string>
#include <string_view>
#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 <Windows.h>
#undef near
#undef far
#else
#include <pthread.h>
#ifdef __APPLE__
#include <malloc/malloc.h>
#include <dispatch/dispatch.h>
#else
#include <malloc.h>
#include <semaphore.h>
#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<uint32_t>(SkColorChannel::kR),
kGreen_SkColorChannelFlag = 1 << static_cast<uint32_t>(SkColorChannel::kG),
kBlue_SkColorChannelFlag = 1 << static_cast<uint32_t>(SkColorChannel::kB),
kAlpha_SkColorChannelFlag = 1 << static_cast<uint32_t>(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<kUnpremul_SkAlphaType>. For convenience, this type can also be referred to
as SkColor4f.
*/
template <SkAlphaType kAT>
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<float, 4> */
std::array<float, 4> 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<kPremul_SkAlphaType> 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<kUnpremul_SkAlphaType> 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<kUnpremul_SkAlphaType>;
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<int>(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<SkColorSpace> cs);
SkColorInfo(const SkColorInfo&);
SkColorInfo(SkColorInfo&&);
SkColorInfo& operator=(const SkColorInfo&);
SkColorInfo& operator=(SkColorInfo&&);
SkColorSpace* colorSpace() const;
sk_sp<SkColorSpace> 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<SkColorSpace> 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<SkColorSpace> 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<SkColorSpace> cs);
static SkImageInfo Make(SkISize dimensions, SkColorType ct, SkAlphaType at);
static SkImageInfo Make(SkISize dimensions, SkColorType ct, SkAlphaType at,
sk_sp<SkColorSpace> 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<SkColorSpace> 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<SkColorSpace> 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<SkColorSpace> 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<SkColorSpace> 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<SkColorSpace> 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<int32_t>(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<int>(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<int>(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<SkColorSpace> 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<SkColorSpace> 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<const uint8_t*>(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<const uint16_t*>(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<const uint32_t*>(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<const uint64_t*>(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<const uint16_t*>(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<const uint16_t*>(this->addr64(x, y));
}
/** Returns writable base pixel address.
@return writable generic base pointer to pixels
*/
void* writable_addr() const { return const_cast<void*>(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<void*>(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<uint8_t*>(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<uint16_t*>(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<uint32_t*>(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<uint64_t*>(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<uint16_t*>(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<SkColorSpace> 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<SkPixelRef> 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<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy, const SkSamplingOptions&,
const SkMatrix* localMatrix = nullptr) const;
sk_sp<SkShader> makeShader(SkTileMode tmx, SkTileMode tmy, const SkSamplingOptions& sampling,
const SkMatrix& lm) const;
/** Defaults to clamp in both X and Y. */
sk_sp<SkShader> makeShader(const SkSamplingOptions& sampling, const SkMatrix& lm) const;
sk_sp<SkShader> 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<SkImage> 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<SkPixelRef> fPixelRef;
SkPixmap fPixmap;
sk_sp<SkMipmap> 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<int>(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<SkFlattenable> (*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<SkData> serialize(const SkSerialProcs* = nullptr) const;
size_t serialize(void* memory, size_t memory_size,
const SkSerialProcs* = nullptr) const;
static sk_sp<SkFlattenable> 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<SkFlattenable> 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<SkFlattenable> 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<SkImageFilter> makeWithLocalMatrix(const SkMatrix& matrix) const;
static sk_sp<SkImageFilter> Deserialize(const void* data, size_t size,
const SkDeserialProcs* procs = nullptr) {
return sk_sp<SkImageFilter>(static_cast<SkImageFilter*>(
SkFlattenable::Deserialize(kSkImageFilter_Type, data, size, procs).release()));
}
protected:
sk_sp<SkImageFilter> refMe() const {
return sk_ref_sp(const_cast<SkImageFilter*>(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<unsigned>(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<unsigned>(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<uint8_t>(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<SkShader> 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<SkShader> 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<SkColorFilter> 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<SkColorFilter> 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<SkBlendMode> 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<SkBlender> 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<SkBlender> 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<SkPathEffect> 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<SkPathEffect> 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<SkMaskFilter> 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<SkMaskFilter> 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<SkImageFilter> 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<SkImageFilter> 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<SkPathEffect> fPathEffect;
sk_sp<SkShader> fShader;
sk_sp<SkMaskFilter> fMaskFilter;
sk_sp<SkColorFilter> fColorFilter;
sk_sp<SkImageFilter> fImageFilter;
sk_sp<SkBlender> 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<decltype(fPathEffect)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fShader)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fMaskFilter)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fColorFilter)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fImageFilter)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fBlender)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fColor4f)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fBitfields)>::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<SkCanvas> canvas = SkRasterHandleAllocator::MakeCanvas(
* SkImageInfo::Make(...),
* std::make_unique<MySubclassRasterHandleAllocator>(...),
* 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<SkCanvas> MakeCanvas(std::unique_ptr<SkRasterHandleAllocator>,
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<Rec> 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<int32_t> fRefCnt;
char fBeginningOfData[1] = {'\0'};
// Ensure the unsized delete is called.
void operator delete(void* p) { ::operator delete(p); }
};
sk_sp<Rec> fRec;
static_assert(::sk_is_trivially_relocatable<decltype(fRec)>::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<SkCanvas> 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<SkCanvas> 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<SkDevice> 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<SkSurface> 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<SkShader>, 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<SkImage>& 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<SkImage>& 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<SkImage>& 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<SkImage>& 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<const SkImage> image, const SkRect& srcRect, const SkRect& dstRect,
int matrixIndex, float alpha, unsigned aaFlags, bool hasClip);
ImageSetEntry(sk_sp<const SkImage> image, const SkRect& srcRect, const SkRect& dstRect,
float alpha, unsigned aaFlags);
ImageSetEntry();
~ImageSetEntry();
ImageSetEntry(const ImageSetEntry&);
ImageSetEntry& operator=(const ImageSetEntry&);
sk_sp<const SkImage> 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<SkTextBlob>& 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<SkPicture>& 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<SkPicture>& 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<SkVertices>& 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<SkBlender> 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<SkData>& 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<SkSurface> 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<SkBlender>, 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<SkShader>, SkClipOp);
virtual void onClipRegion(const SkRegion& deviceRgn, SkClipOp op);
virtual void onResetClip();
virtual void onDiscard();
/**
*/
virtual sk_sp<sktext::gpu::Slug> 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<AutoLayerForImageFilter> 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<SkDevice> fDevice;
sk_sp<SkImageFilter> fImageFilter; // applied to layer *before* being drawn by paint
SkPaint fPaint;
bool fIsCoverage;
bool fDiscard;
Layer(sk_sp<SkDevice> device,
sk_sp<SkImageFilter> 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<SkSpecialImage>, SkIPoint);
BackImage(const BackImage&);
BackImage(BackImage&&);
BackImage& operator=(const BackImage&);
~BackImage();
sk_sp<SkSpecialImage> 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<Layer> 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<BackImage> fBackImage;
SkM44 fMatrix;
int fDeferredSaveCount = 0;
MCRec(SkDevice* device);
MCRec(const MCRec* prev);
~MCRec();
void newLayer(sk_sp<SkDevice> layerDevice,
sk_sp<SkImageFilter> 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<SkDevice> fRootDevice;
const SkSurfaceProps fProps;
int fSaveCount; // value returned by getSaveCount()
std::unique_ptr<SkRasterHandleAllocator> 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? <reed>)
friend class SkOverdrawCanvas;
friend class SkRasterHandleAllocator;
friend class SkRecords::Draw;
template <typename Key>
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>,
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<sktext::gpu::Slug> 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<SkDevice>);
// 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<sktext::GlyphRunBuilder> 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<SkData> {
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<const uint8_t*>(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<void*>(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<SkData> 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<SkData> 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<SkData> 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<SkData> 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<SkData> 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<SkData> 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<SkData> 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<SkData> 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<SkData> 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<SkData> 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<SkData> MakeFromStream(SkStream*, size_t size);
/**
* Create a new dataref using a subset of the data in the specified
* src dataref.
*/
static sk_sp<SkData> 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<SkData> MakeEmpty();
private:
friend class SkNVRefCnt<SkData>;
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<SkData> 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 <typename Fn, typename... Args>
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>(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<uint8_t> 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<int> 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<SkIDChangeListener> 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<SkIDChangeListener>> fListeners SK_GUARDED_BY(fMutex);
};
private:
std::atomic<bool> 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<SkPathRef>, 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<SkPathRef> {
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<SkPathRef>* 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 [<i>]).
* 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<SkPoint*, SkScalar*> 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<SkPathRef>* 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<SkPathRef>* 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<SkIDChangeListener>); // 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<SkPoint*, SkScalar*> 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<int> 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<SkPoint>& 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<SkPoint>& 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<SkPathRef>) 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<decltype(fBounds)>::value);
static_assert(::sk_is_trivially_relocatable<decltype(fRunHead)>::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<Type>(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<SkStreamAsset> 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<SkStream> duplicate() const {
return std::unique_ptr<SkStream>(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<SkStream> fork() const {
return std::unique_ptr<SkStream>(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<SkStreamRewindable> duplicate() const {
return std::unique_ptr<SkStreamRewindable>(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<SkStreamSeekable> duplicate() const {
return std::unique_ptr<SkStreamSeekable>(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<SkStreamSeekable> fork() const {
return std::unique_ptr<SkStreamSeekable>(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<SkStreamAsset> duplicate() const {
return std::unique_ptr<SkStreamAsset>(this->onDuplicate());
}
std::unique_ptr<SkStreamAsset> fork() const {
return std::unique_ptr<SkStreamAsset>(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<SkStreamMemory> duplicate() const {
return std::unique_ptr<SkStreamMemory>(this->onDuplicate());
}
std::unique_ptr<SkStreamMemory> fork() const {
return std::unique_ptr<SkStreamMemory>(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<SkFILEStream> Make(const char path[]) {
std::unique_ptr<SkFILEStream> 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<SkStreamAsset> duplicate() const {
return std::unique_ptr<SkStreamAsset>(this->onDuplicate());
}
size_t getPosition() const override;
bool seek(size_t position) override;
bool move(long offset) override;
std::unique_ptr<SkStreamAsset> fork() const {
return std::unique_ptr<SkStreamAsset>(this->onFork());
}
size_t getLength() const override;
private:
explicit SkFILEStream(FILE*, size_t size, size_t start);
explicit SkFILEStream(std::shared_ptr<FILE>, size_t end, size_t start);
explicit SkFILEStream(std::shared_ptr<FILE>, size_t end, size_t start, size_t current);
SkStreamAsset* onDuplicate() const override;
SkStreamAsset* onFork() const override;
std::shared_ptr<FILE> 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<SkData> data);
/** Returns a stream with a copy of the input data. */
static std::unique_ptr<SkMemoryStream> MakeCopy(const void* data, size_t length);
/** Returns a stream with a bare pointer reference to the input data. */
static std::unique_ptr<SkMemoryStream> MakeDirect(const void* data, size_t length);
/** Returns a stream with a shared reference to the input data. */
static std::unique_ptr<SkMemoryStream> Make(sk_sp<SkData> 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<SkData> asData() const { return fData; }
void setData(sk_sp<SkData> 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<SkMemoryStream> duplicate() const {
return std::unique_ptr<SkMemoryStream>(this->onDuplicate());
}
size_t getPosition() const override;
bool seek(size_t position) override;
bool move(long offset) override;
std::unique_ptr<SkMemoryStream> fork() const {
return std::unique_ptr<SkMemoryStream>(this->onFork());
}
size_t getLength() const override;
const void* getMemoryBase() override;
private:
SkMemoryStream* onDuplicate() const override;
SkMemoryStream* onFork() const override;
sk_sp<SkData> 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<SkData> detachAsData();
/** Reset, returning a reader stream with the current content. */
std::unique_ptr<SkStreamAsset> 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 <typename T> static constexpr T SkAlign2(T x) { return (x + 1) >> 1 << 1; }
template <typename T> static constexpr T SkAlign4(T x) { return (x + 3) >> 2 << 2; }
template <typename T> static constexpr T SkAlign8(T x) { return (x + 7) >> 3 << 3; }
template <typename T> static constexpr bool SkIsAlign2(T x) { return 0 == (x & 1); }
template <typename T> static constexpr bool SkIsAlign4(T x) { return 0 == (x & 3); }
template <typename T> static constexpr bool SkIsAlign8(T x) { return 0 == (x & 7); }
template <typename T> static constexpr T SkAlignPtr(T x) {
return sizeof(void*) == 8 ? SkAlign8(x) : SkAlign4(x);
}
template <typename T> 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 <dest, source> pattern used by boost.
template <typename D, typename S> struct copy_const {
using type = std::conditional_t<std::is_const<S>::value, std::add_const_t<D>, D>;
};
template <typename D, typename S> using copy_const_t = typename copy_const<D, S>::type;
template <typename D, typename S> struct copy_volatile {
using type = std::conditional_t<std::is_volatile<S>::value, std::add_volatile_t<D>, D>;
};
template <typename D, typename S> using copy_volatile_t = typename copy_volatile<D, S>::type;
template <typename D, typename S> struct copy_cv {
using type = copy_volatile_t<copy_const_t<D, S>, S>;
};
template <typename D, typename S> using copy_cv_t = typename copy_cv<D, S>::type;
// The name 'same' here means 'overwrite'.
// Alternate proposed names are 'replace', 'transfer', or 'qualify_from'.
// same_xxx<D, S> can be written as copy_xxx<remove_xxx_t<D>, S>
template <typename D, typename S> using same_const = copy_const<std::remove_const_t<D>, S>;
template <typename D, typename S> using same_const_t = typename same_const<D, S>::type;
template <typename D, typename S> using same_volatile =copy_volatile<std::remove_volatile_t<D>,S>;
template <typename D, typename S> using same_volatile_t = typename same_volatile<D, S>::type;
template <typename D, typename S> using same_cv = copy_cv<std::remove_cv_t<D>, S>;
template <typename D, typename S> using same_cv_t = typename same_cv<D, S>::type;
} // namespace sknonstd
template <typename Container>
constexpr int SkCount(const Container& c) { return SkTo<int>(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<typename T> 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 <typename T> 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 <typename D, typename S> inline D* SkTAfter(S* ptr, size_t count = 1) {
return reinterpret_cast<D*>(ptr + count);
}
/**
* Returns a pointer to a D which comes byteOffset bytes after S.
*/
template <typename D, typename S> 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<D*>(reinterpret_cast<sknonstd::same_cv_t<char, D>*>(ptr) + byteOffset);
}
template <typename T, T* P> struct SkOverloadedFunctionObject {
template <typename... Args>
auto operator()(Args&&... args) const -> decltype(P(std::forward<Args>(args)...)) {
return P(std::forward<Args>(args)...);
}
};
template <auto F> using SkFunctionObject =
SkOverloadedFunctionObject<std::remove_pointer_t<decltype(F)>, 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 <typename T, void (*P)(T*)> class SkAutoTCallVProc
: public std::unique_ptr<T, SkFunctionObject<P>> {
using inherited = std::unique_ptr<T, SkFunctionObject<P>>;
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 <typename T> class AutoTArray {
public:
AutoTArray() {}
// Allocate size number of T elements
explicit AutoTArray(size_t size) {
fSize = check_size_bytes_too_big<T>(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<T[]> fData;
size_t fSize = 0;
};
/** Wraps AutoTArray, with room for kCountRequested elements preallocated.
*/
template <int kCountRequested, typename T> 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 <typename T,
typename = std::enable_if_t<std::is_trivially_default_constructible<T>::value &&
std::is_trivially_destructible<T>::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<T, SkOverloadedFunctionObject<void(void*), sk_free>> fPtr;
};
template <size_t kCountRequested,
typename T,
typename = std::enable_if_t<std::is_trivially_default_constructible<T>::value &&
std::is_trivially_destructible<T>::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<void, SkOverloadedFunctionObject<void(void*), sk_free>>;
} // namespace skia_private
template<typename C, std::size_t... Is>
constexpr auto SkMakeArrayFromIndexSequence(C c, std::index_sequence<Is...> is)
-> std::array<decltype(c(std::declval<typename decltype(is)::value_type>())), sizeof...(Is)> {
return {{ c(Is)... }};
}
template<size_t N, typename C> constexpr auto SkMakeArray(C c)
-> std::array<decltype(c(std::declval<typename std::index_sequence<N>::value_type>())), N> {
return SkMakeArrayFromIndexSequence(c, std::make_index_sequence<N>{});
}
/** @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 <typename T>
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<const uint32_t, 47> SkFibonacci47;
template<uint32_t kMaxSize>
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<size_t>(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<char[]> 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 <typename Ctor>
auto make(Ctor&& ctor) -> decltype(ctor(nullptr)) {
using T = std::remove_pointer_t<decltype(ctor(nullptr))>;
uint32_t size = SkToU32(sizeof(T));
uint32_t alignment = SkToU32(alignof(T));
char* objStart;
if (std::is_trivially_destructible<T>::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 <typename T, typename... Args>
T* make(Args&&... args) {
return this->make([&](void* objStart) {
return new(objStart) T(std::forward<Args>(args)...);
});
}
template <typename T>
T* make() {
if constexpr (std::is_standard_layout<T>::value && std::is_trivial<T>::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 <typename T>
T* makeArrayDefault(size_t count) {
T* array = this->allocUninitializedArray<T>(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 <typename T>
T* makeArray(size_t count) {
T* array = this->allocUninitializedArray<T>(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 <typename T, typename Initializer>
T* makeInitializedArray(size_t count, Initializer initializer) {
T* array = this->allocUninitializedArray<T>(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<uint32_t>(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 <typename T>
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<uintptr_t>(fCursor) + 1) & mask;
uintptr_t totalSize = size + alignedOffset;
AssertRelease(totalSize >= size);
if (totalSize > static_cast<uintptr_t>(fEnd - fCursor)) {
this->ensureSpace(size, alignment);
alignedOffset = (~reinterpret_cast<uintptr_t>(fCursor) + 1) & mask;
}
char* object = fCursor + alignedOffset;
SkASSERT((reinterpret_cast<uintptr_t>(object) & (alignment - 1)) == 0);
SkASSERT(object + size <= fEnd);
return object;
}
char* allocObjectWithFooter(uint32_t sizeIncludingFooter, uint32_t alignment);
template <typename T>
T* allocUninitializedArray(size_t countZ) {
AssertRelease(SkTFitsIn<uint32_t>(countZ));
uint32_t count = SkToU32(countZ);
char* objStart;
AssertRelease(count <= std::numeric_limits<uint32_t>::max() / sizeof(T));
uint32_t arraySize = SkToU32(count * sizeof(T));
uint32_t alignment = SkToU32(alignof(T));
if (std::is_trivially_destructible<T>::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<uint32_t>::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<std::numeric_limits<uint32_t>::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 <size_t InlineStorageSize>
class SkSTArenaAlloc : private std::array<char, InlineStorageSize>, 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 <size_t InlineStorageSize>
class SkSTArenaAllocWithReset
: private std::array<char, InlineStorageSize>, 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<double, 2> 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<double, 4> ConvertToPolynomial(const double curve[8], bool yValues);
static SkSpan<const float> IntersectWithHorizontalLine(
SkSpan<const SkPoint> controlPoints, float yIntercept,
float intersectionStorage[3]);
static SkSpan<const float> 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<const float> IntersectWithHorizontalLine(
SkSpan<const SkPoint> 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<const float> 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<int>::min() - b) {
fOK = false;
return a;
} else if (b > 0 && a > std::numeric_limits<int>::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 <typename T> T castTo(size_t value) {
if (!SkTFitsIn<T>(value)) {
fOK = false;
}
return static_cast<T>(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<uint64_t>::max() >> 32
&& y <= std::numeric_limits<uint64_t>::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 <typename T> const T* skipCount(size_t count) {
return static_cast<const T*>(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 <typename In, typename Out>
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<Out>(d);
*mod = static_cast<Out>(numer-d*denom);
#else
// On x86 this will just be a single idiv.
*div = static_cast<Out>(numer/denom);
*mod = static_cast<Out>(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<size_t>(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<int>(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<uint32_t>(value));
}
constexpr int SkNextPow2_portable(int value) {
SkASSERT(value > 0);
return 1 << SkNextLog2_portable(static_cast<uint32_t>(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<uint32_t>(value));
}
constexpr int SkPrevPow2_portable(int value) {
SkASSERT(value > 0);
return 1 << SkPrevLog2_portable(static_cast<uint32_t>(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 <typename T> 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 <typename T> class SkTLazy {
public:
SkTLazy() = default;
explicit SkTLazy(const T* src) : fValue(src ? std::optional<T>(*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 <typename... Args> T* init(Args&&... args) {
fValue.emplace(std::forward<Args>(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<T> 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> 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 <typename T>
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 <typename... Args>
void initIfNeeded(Args&&... args) {
if (!fObj) {
SkASSERT(!fLazy.has_value());
fObj = &fLazy.emplace(std::forward<Args>(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<T> 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 <typename T, typename C>
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 <typename T, typename C>
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 <typename T, typename C> 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 <typename T> 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 <typename T, typename C>
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 <typename T, typename C>
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 <typename T, typename C>
void SkTIntroSort(int depth, T* left, int count, const C& lessThan) {
for (;;) {
if (count <= 32) {
SkTInsertionSort(left, count, lessThan);
return;
}
if (depth == 0) {
SkTHeapSort<T>(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 <typename T, typename C>
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 <typename T> 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 <typename T> 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 <typename T, typename P>
static SK_ALWAYS_INLINE T sk_unaligned_load(const P* ptr) {
static_assert(std::is_trivially_copyable<T>::value);
T val;
memcpy(&val, ptr, sizeof(val));
return val;
}
template <typename T, typename P>
static SK_ALWAYS_INLINE void sk_unaligned_store(P* ptr, T val) {
static_assert(std::is_trivially_copyable<T>::value);
memcpy(ptr, &val, sizeof(val));
}
// Copy the bytes from src into an instance of type Dst and return it.
template <typename Dst, typename Src>
static SK_ALWAYS_INLINE Dst sk_bit_cast(const Src& src) {
static_assert(sizeof(Dst) == sizeof(Src));
static_assert(std::is_trivially_copyable<Dst>::value);
static_assert(std::is_trivially_copyable<Src>::value);
return sk_unaligned_load<Dst>(&src);
}
#ifndef SKVX_DEFINED
#define SKVX_DEFINED
// skvx::Vec<N,T> are SIMD vectors of N T's, a v1.5 successor to SkNx<N,T>.
//
// 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<N,T> 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 <int N > SI
#define SINT template <int N, typename T> SI
#define SINTU template <int N, typename T, typename U, \
typename=std::enable_if_t<std::is_convertible<U,T>::value>> SI
namespace skvx {
template <int N, typename T>
struct alignas(N*sizeof(T)) Vec;
template <int... Ix, int N, typename T>
SI Vec<sizeof...(Ix),T> shuffle(const Vec<N,T>&);
// All Vec have the same simple memory layout, the same as `T vec[N]`.
template <int N, typename T>
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<T> 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<N/2,T>::Load(vals + 0);
this->hi = Vec<N/2,T>::Load(vals + N/2);
}
SKVX_ALWAYS_INLINE T operator[](int i) const { return i<N/2 ? this->lo[i] : this->hi[i-N/2]; }
SKVX_ALWAYS_INLINE T& operator[](int i) { return i<N/2 ? this->lo[i] : this->hi[i-N/2]; }
SKVX_ALWAYS_INLINE static Vec Load(const void* ptr) {
return sk_unaligned_load<Vec>(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<N/2,T> 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 <typename T>
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<T> 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<Vec>(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 <typename T>
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<T> 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<Vec>(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 <typename T>
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<T> 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<Vec>(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 <typename T> struct Mask { using type = T; };
template <> struct Mask<float > { using type = int32_t; };
template <> struct Mask<double> { using type = int64_t; };
template <typename T> using M = typename Mask<T>::type;
// Join two Vec<N,T> into one Vec<2N,T>.
SINT Vec<2*N,T> join(const Vec<N,T>& lo, const Vec<N,T>& 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<N,T>,
// or often integrate them directly into the recursion of style 3), allowing fine control.
#if SKVX_USE_SIMD && (defined(__clang__) || defined(__GNUC__))
// VExt<N,T> types have the same size as Vec<N,T> and support most operations directly.
#if defined(__clang__)
template <int N, typename T>
using VExt = T __attribute__((ext_vector_type(N)));
#elif defined(__GNUC__)
template <int N, typename T>
struct VExtHelper {
typedef T __attribute__((vector_size(N*sizeof(T)))) type;
};
template <int N, typename T>
using VExt = typename VExtHelper<N,T>::type;
// For some reason some (new!) versions of GCC cannot seem to deduce N in the generic
// to_vec<N,T>() 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<Vec<4,float>>(v); }
#endif
SINT VExt<N,T> to_vext(const Vec<N,T>& v) { return sk_bit_cast<VExt<N,T>>(v); }
SINT Vec <N,T> to_vec(const VExt<N,T>& v) { return sk_bit_cast<Vec <N,T>>(v); }
SINT Vec<N,T> operator+(const Vec<N,T>& x, const Vec<N,T>& y) {
return to_vec<N,T>(to_vext(x) + to_vext(y));
}
SINT Vec<N,T> operator-(const Vec<N,T>& x, const Vec<N,T>& y) {
return to_vec<N,T>(to_vext(x) - to_vext(y));
}
SINT Vec<N,T> operator*(const Vec<N,T>& x, const Vec<N,T>& y) {
return to_vec<N,T>(to_vext(x) * to_vext(y));
}
SINT Vec<N,T> operator/(const Vec<N,T>& x, const Vec<N,T>& y) {
return to_vec<N,T>(to_vext(x) / to_vext(y));
}
SINT Vec<N,T> operator^(const Vec<N,T>& x, const Vec<N,T>& y) {
return to_vec<N,T>(to_vext(x) ^ to_vext(y));
}
SINT Vec<N,T> operator&(const Vec<N,T>& x, const Vec<N,T>& y) {
return to_vec<N,T>(to_vext(x) & to_vext(y));
}
SINT Vec<N,T> operator|(const Vec<N,T>& x, const Vec<N,T>& y) {
return to_vec<N,T>(to_vext(x) | to_vext(y));
}
SINT Vec<N,T> operator!(const Vec<N,T>& x) { return to_vec<N,T>(!to_vext(x)); }
SINT Vec<N,T> operator-(const Vec<N,T>& x) { return to_vec<N,T>(-to_vext(x)); }
SINT Vec<N,T> operator~(const Vec<N,T>& x) { return to_vec<N,T>(~to_vext(x)); }
SINT Vec<N,T> operator<<(const Vec<N,T>& x, int k) { return to_vec<N,T>(to_vext(x) << k); }
SINT Vec<N,T> operator>>(const Vec<N,T>& x, int k) { return to_vec<N,T>(to_vext(x) >> k); }
SINT Vec<N,M<T>> operator==(const Vec<N,T>& x, const Vec<N,T>& y) {
return sk_bit_cast<Vec<N,M<T>>>(to_vext(x) == to_vext(y));
}
SINT Vec<N,M<T>> operator!=(const Vec<N,T>& x, const Vec<N,T>& y) {
return sk_bit_cast<Vec<N,M<T>>>(to_vext(x) != to_vext(y));
}
SINT Vec<N,M<T>> operator<=(const Vec<N,T>& x, const Vec<N,T>& y) {
return sk_bit_cast<Vec<N,M<T>>>(to_vext(x) <= to_vext(y));
}
SINT Vec<N,M<T>> operator>=(const Vec<N,T>& x, const Vec<N,T>& y) {
return sk_bit_cast<Vec<N,M<T>>>(to_vext(x) >= to_vext(y));
}
SINT Vec<N,M<T>> operator< (const Vec<N,T>& x, const Vec<N,T>& y) {
return sk_bit_cast<Vec<N,M<T>>>(to_vext(x) < to_vext(y));
}
SINT Vec<N,M<T>> operator> (const Vec<N,T>& x, const Vec<N,T>& y) {
return sk_bit_cast<Vec<N,M<T>>>(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<T>> operator==(const Vec<1,T>& x, const Vec<1,T>& y) {
return x.val == y.val ? ~0 : 0;
}
SIT Vec<1,M<T>> operator!=(const Vec<1,T>& x, const Vec<1,T>& y) {
return x.val != y.val ? ~0 : 0;
}
SIT Vec<1,M<T>> operator<=(const Vec<1,T>& x, const Vec<1,T>& y) {
return x.val <= y.val ? ~0 : 0;
}
SIT Vec<1,M<T>> operator>=(const Vec<1,T>& x, const Vec<1,T>& y) {
return x.val >= y.val ? ~0 : 0;
}
SIT Vec<1,M<T>> operator< (const Vec<1,T>& x, const Vec<1,T>& y) {
return x.val < y.val ? ~0 : 0;
}
SIT Vec<1,M<T>> 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<N,T> operator+(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo + y.lo, x.hi + y.hi);
}
SINT Vec<N,T> operator-(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo - y.lo, x.hi - y.hi);
}
SINT Vec<N,T> operator*(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo * y.lo, x.hi * y.hi);
}
SINT Vec<N,T> operator/(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo / y.lo, x.hi / y.hi);
}
SINT Vec<N,T> operator^(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo ^ y.lo, x.hi ^ y.hi);
}
SINT Vec<N,T> operator&(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo & y.lo, x.hi & y.hi);
}
SINT Vec<N,T> operator|(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo | y.lo, x.hi | y.hi);
}
SINT Vec<N,T> operator!(const Vec<N,T>& x) { return join(!x.lo, !x.hi); }
SINT Vec<N,T> operator-(const Vec<N,T>& x) { return join(-x.lo, -x.hi); }
SINT Vec<N,T> operator~(const Vec<N,T>& x) { return join(~x.lo, ~x.hi); }
SINT Vec<N,T> operator<<(const Vec<N,T>& x, int k) { return join(x.lo << k, x.hi << k); }
SINT Vec<N,T> operator>>(const Vec<N,T>& x, int k) { return join(x.lo >> k, x.hi >> k); }
SINT Vec<N,M<T>> operator==(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo == y.lo, x.hi == y.hi);
}
SINT Vec<N,M<T>> operator!=(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo != y.lo, x.hi != y.hi);
}
SINT Vec<N,M<T>> operator<=(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo <= y.lo, x.hi <= y.hi);
}
SINT Vec<N,M<T>> operator>=(const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo >= y.lo, x.hi >= y.hi);
}
SINT Vec<N,M<T>> operator< (const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo < y.lo, x.hi < y.hi);
}
SINT Vec<N,M<T>> operator> (const Vec<N,T>& x, const Vec<N,T>& y) {
return join(x.lo > y.lo, x.hi > y.hi);
}
#endif
// Scalar/vector operations splat the scalar to a vector.
SINTU Vec<N,T> operator+ (U x, const Vec<N,T>& y) { return Vec<N,T>(x) + y; }
SINTU Vec<N,T> operator- (U x, const Vec<N,T>& y) { return Vec<N,T>(x) - y; }
SINTU Vec<N,T> operator* (U x, const Vec<N,T>& y) { return Vec<N,T>(x) * y; }
SINTU Vec<N,T> operator/ (U x, const Vec<N,T>& y) { return Vec<N,T>(x) / y; }
SINTU Vec<N,T> operator^ (U x, const Vec<N,T>& y) { return Vec<N,T>(x) ^ y; }
SINTU Vec<N,T> operator& (U x, const Vec<N,T>& y) { return Vec<N,T>(x) & y; }
SINTU Vec<N,T> operator| (U x, const Vec<N,T>& y) { return Vec<N,T>(x) | y; }
SINTU Vec<N,M<T>> operator==(U x, const Vec<N,T>& y) { return Vec<N,T>(x) == y; }
SINTU Vec<N,M<T>> operator!=(U x, const Vec<N,T>& y) { return Vec<N,T>(x) != y; }
SINTU Vec<N,M<T>> operator<=(U x, const Vec<N,T>& y) { return Vec<N,T>(x) <= y; }
SINTU Vec<N,M<T>> operator>=(U x, const Vec<N,T>& y) { return Vec<N,T>(x) >= y; }
SINTU Vec<N,M<T>> operator< (U x, const Vec<N,T>& y) { return Vec<N,T>(x) < y; }
SINTU Vec<N,M<T>> operator> (U x, const Vec<N,T>& y) { return Vec<N,T>(x) > y; }
SINTU Vec<N,T> operator+ (const Vec<N,T>& x, U y) { return x + Vec<N,T>(y); }
SINTU Vec<N,T> operator- (const Vec<N,T>& x, U y) { return x - Vec<N,T>(y); }
SINTU Vec<N,T> operator* (const Vec<N,T>& x, U y) { return x * Vec<N,T>(y); }
SINTU Vec<N,T> operator/ (const Vec<N,T>& x, U y) { return x / Vec<N,T>(y); }
SINTU Vec<N,T> operator^ (const Vec<N,T>& x, U y) { return x ^ Vec<N,T>(y); }
SINTU Vec<N,T> operator& (const Vec<N,T>& x, U y) { return x & Vec<N,T>(y); }
SINTU Vec<N,T> operator| (const Vec<N,T>& x, U y) { return x | Vec<N,T>(y); }
SINTU Vec<N,M<T>> operator==(const Vec<N,T>& x, U y) { return x == Vec<N,T>(y); }
SINTU Vec<N,M<T>> operator!=(const Vec<N,T>& x, U y) { return x != Vec<N,T>(y); }
SINTU Vec<N,M<T>> operator<=(const Vec<N,T>& x, U y) { return x <= Vec<N,T>(y); }
SINTU Vec<N,M<T>> operator>=(const Vec<N,T>& x, U y) { return x >= Vec<N,T>(y); }
SINTU Vec<N,M<T>> operator< (const Vec<N,T>& x, U y) { return x < Vec<N,T>(y); }
SINTU Vec<N,M<T>> operator> (const Vec<N,T>& x, U y) { return x > Vec<N,T>(y); }
SINT Vec<N,T>& operator+=(Vec<N,T>& x, const Vec<N,T>& y) { return (x = x + y); }
SINT Vec<N,T>& operator-=(Vec<N,T>& x, const Vec<N,T>& y) { return (x = x - y); }
SINT Vec<N,T>& operator*=(Vec<N,T>& x, const Vec<N,T>& y) { return (x = x * y); }
SINT Vec<N,T>& operator/=(Vec<N,T>& x, const Vec<N,T>& y) { return (x = x / y); }
SINT Vec<N,T>& operator^=(Vec<N,T>& x, const Vec<N,T>& y) { return (x = x ^ y); }
SINT Vec<N,T>& operator&=(Vec<N,T>& x, const Vec<N,T>& y) { return (x = x & y); }
SINT Vec<N,T>& operator|=(Vec<N,T>& x, const Vec<N,T>& y) { return (x = x | y); }
SINTU Vec<N,T>& operator+=(Vec<N,T>& x, U y) { return (x = x + Vec<N,T>(y)); }
SINTU Vec<N,T>& operator-=(Vec<N,T>& x, U y) { return (x = x - Vec<N,T>(y)); }
SINTU Vec<N,T>& operator*=(Vec<N,T>& x, U y) { return (x = x * Vec<N,T>(y)); }
SINTU Vec<N,T>& operator/=(Vec<N,T>& x, U y) { return (x = x / Vec<N,T>(y)); }
SINTU Vec<N,T>& operator^=(Vec<N,T>& x, U y) { return (x = x ^ Vec<N,T>(y)); }
SINTU Vec<N,T>& operator&=(Vec<N,T>& x, U y) { return (x = x & Vec<N,T>(y)); }
SINTU Vec<N,T>& operator|=(Vec<N,T>& x, U y) { return (x = x | Vec<N,T>(y)); }
SINT Vec<N,T>& operator<<=(Vec<N,T>& x, int bits) { return (x = x << bits); }
SINT Vec<N,T>& operator>>=(Vec<N,T>& 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<N,T> naive_if_then_else(const Vec<N,M<T>>& cond, const Vec<N,T>& t, const Vec<N,T>& e) {
return sk_bit_cast<Vec<N,T>>(( cond & sk_bit_cast<Vec<N, M<T>>>(t)) |
(~cond & sk_bit_cast<Vec<N, M<T>>>(e)) );
}
SIT Vec<1,T> if_then_else(const Vec<1,M<T>>& 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<Vec<1,T>>(( cond & sk_bit_cast<Vec<1, M<T>>>(t)) |
(~cond & sk_bit_cast<Vec<1, M<T>>>(e)) );
}
SINT Vec<N,T> if_then_else(const Vec<N,M<T>>& cond, const Vec<N,T>& t, const Vec<N,T>& 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<Vec<N,T>>(_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<Vec<N,T>>(_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<Vec<N,T>>(vbslq_u8(sk_bit_cast<uint8x16_t>(cond),
sk_bit_cast<uint8x16_t>(t),
sk_bit_cast<uint8x16_t>(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<N,T>& 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<uint8x8_t> (x)) > 0; }
if constexpr (N*sizeof(T) == 16) { return vmaxvq_u8(sk_bit_cast<uint8x16_t>(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<VExt<4,int>>(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<N,T>& 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<uint8x8_t> (x)) > 0;}
if constexpr (sizeof(T)==1 && N==16) {return vminvq_u8 (sk_bit_cast<uint8x16_t>(x)) > 0;}
if constexpr (sizeof(T)==2 && N==4) {return vminv_u16 (sk_bit_cast<uint16x4_t>(x)) > 0;}
if constexpr (sizeof(T)==2 && N==8) {return vminvq_u16(sk_bit_cast<uint16x8_t>(x)) > 0;}
if constexpr (sizeof(T)==4 && N==2) {return vminv_u32 (sk_bit_cast<uint32x2_t>(x)) > 0;}
if constexpr (sizeof(T)==4 && N==4) {return vminvq_u32(sk_bit_cast<uint32x4_t>(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<VExt<4,int>>(x));
}
#endif
return all(x.lo)
&& all(x.hi);
}
// cast() Vec<N,S> to Vec<N,D>, as if applying a C-cast to each lane.
// TODO: implement with map()?
template <typename D, typename S>
SI Vec<1,D> cast(const Vec<1,S>& src) { return (D)src.val; }
template <typename D, int N, typename S>
SI Vec<N,D> cast(const Vec<N,S>& src) {
#if SKVX_USE_SIMD && defined(__clang__)
return to_vec(__builtin_convertvector(to_vext(src), VExt<N,D>));
#else
return join(cast<D>(src.lo), cast<D>(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<N,T>& x) { return std::min(min(x.lo), min(x.hi)); }
SINT T max(const Vec<N,T>& x) { return std::max(max(x.lo), max(x.hi)); }
SINT Vec<N,T> min(const Vec<N,T>& x, const Vec<N,T>& y) { return naive_if_then_else(y < x, y, x); }
SINT Vec<N,T> max(const Vec<N,T>& x, const Vec<N,T>& y) { return naive_if_then_else(x < y, y, x); }
SINTU Vec<N,T> min(const Vec<N,T>& x, U y) { return min(x, Vec<N,T>(y)); }
SINTU Vec<N,T> max(const Vec<N,T>& x, U y) { return max(x, Vec<N,T>(y)); }
SINTU Vec<N,T> min(U x, const Vec<N,T>& y) { return min(Vec<N,T>(x), y); }
SINTU Vec<N,T> max(U x, const Vec<N,T>& y) { return max(Vec<N,T>(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<N,T> pin(const Vec<N,T>& x, const Vec<N,T>& lo, const Vec<N,T>& 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 <int... Ix, int N, typename T>
SI Vec<sizeof...(Ix),T> shuffle(const Vec<N,T>& x) {
#if SKVX_USE_SIMD && defined(__clang__)
// TODO: can we just always use { x[Ix]... }?
return to_vec<sizeof...(Ix),T>(__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 <typename Fn, typename... Args, size_t... I>
SI auto map(std::index_sequence<I...>,
Fn&& fn, const Args&... args) -> skvx::Vec<sizeof...(I), decltype(fn(args[0]...))> {
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<int>(i)]...); };
return { lane(I)... };
}
template <typename Fn, int N, typename T, typename... Rest>
auto map(Fn&& fn, const Vec<N,T>& 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<N>{}, fn, first,rest...);
}
SIN Vec<N,float> ceil(const Vec<N,float>& x) { return map( ceilf, x); }
SIN Vec<N,float> floor(const Vec<N,float>& x) { return map(floorf, x); }
SIN Vec<N,float> trunc(const Vec<N,float>& x) { return map(truncf, x); }
SIN Vec<N,float> round(const Vec<N,float>& x) { return map(roundf, x); }
SIN Vec<N,float> sqrt(const Vec<N,float>& x) { return map( sqrtf, x); }
SIN Vec<N,float> abs(const Vec<N,float>& x) { return map( fabsf, x); }
SIN Vec<N,float> fma(const Vec<N,float>& x,
const Vec<N,float>& y,
const Vec<N,float>& 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<N,int> lrint(const Vec<N,float>& x) {
#if SKVX_USE_SIMD && SK_CPU_SSE_LEVEL >= SK_CPU_SSE_LEVEL_AVX
if constexpr (N == 8) {
return sk_bit_cast<Vec<N,int>>(_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<Vec<N,int>>(_mm_cvtps_epi32(sk_bit_cast<__m128>(x)));
}
#endif
return join(lrint(x.lo),
lrint(x.hi));
}
SIN Vec<N,float> fract(const Vec<N,float>& 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<N,uint16_t> to_half_finite_ftz(const Vec<N,float>& x) {
Vec<N,uint32_t> sem = sk_bit_cast<Vec<N,uint32_t>>(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<uint16_t>((s>>16) | (is_norm & norm));
}
SIN Vec<N,float> from_half_finite_ftz(const Vec<N,uint16_t>& x) {
Vec<N,uint32_t> wide = cast<uint32_t>(x),
s = wide & 0x8000,
em = wide ^ s,
is_norm = em > 0x3ff,
norm = (em<<13) + ((127-15)<<23);
return sk_bit_cast<Vec<N,float>>((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<N,uint16_t> to_half(const Vec<N,float>& x) {
#if SKVX_USE_SIMD && defined(__aarch64__)
if constexpr (N == 4) {
return sk_bit_cast<Vec<N,uint16_t>>(vcvt_f16_f32(sk_bit_cast<float32x4_t>(x)));
}
#endif
if constexpr (N > 4) {
return join(to_half(x.lo),
to_half(x.hi));
}
return to_half_finite_ftz(x);
}
SIN Vec<N,float> from_half(const Vec<N,uint16_t>& x) {
#if SKVX_USE_SIMD && defined(__aarch64__)
if constexpr (N == 4) {
return sk_bit_cast<Vec<N,float>>(vcvt_f32_f16(sk_bit_cast<float16x4_t>(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<N,uint8_t> div255(const Vec<N,uint16_t>& x) {
return cast<uint8_t>( (x+127)/255 );
}
// approx_scale(x,y) approximates div255(cast<uint16_t>(x)*cast<uint16_t>(y)) within a bit,
// and is always perfect when x or y is 0 or 255.
SIN Vec<N,uint8_t> approx_scale(const Vec<N,uint8_t>& x, const Vec<N,uint8_t>& 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<uint16_t>(x),
Y = cast<uint16_t>(y);
return cast<uint8_t>( (X*Y+X)/256 );
}
// saturated_add(x,y) sums values and clamps to the maximum value instead of overflowing.
SINT std::enable_if_t<std::is_unsigned_v<T>, Vec<N,T>> saturated_add(const Vec<N,T>& x,
const Vec<N,T>& 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<Vec<N,T>>(_mm_adds_epu8(sk_bit_cast<__m128i>(x),
sk_bit_cast<__m128i>(y)));
#else // SK_ARM_HAS_NEON
return sk_bit_cast<Vec<N,T>>(vqaddq_u8(sk_bit_cast<uint8x16_t>(x),
sk_bit_cast<uint8x16_t>(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<N,T>(std::numeric_limits<T>::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<uint32_t>((cast<uint64_t>(numerator) * fDivisorFactor) >> 32);
#endif
}
uint32_t half() const { return fHalf; }
private:
const uint32_t fDivisorFactor;
const uint32_t fHalf;
};
SIN Vec<N,uint16_t> mull(const Vec<N,uint8_t>& x,
const Vec<N,uint8_t>& 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<uint16_t>(x) * cast<uint16_t>(y);
#endif
}
SIN Vec<N,uint32_t> mull(const Vec<N,uint16_t>& x,
const Vec<N,uint16_t>& 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<uint32_t>(x) * cast<uint32_t>(y);
#endif
}
SIN Vec<N,uint16_t> mulhi(const Vec<N,uint16_t>& x,
const Vec<N,uint16_t>& 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<Vec<8,uint16_t>>(_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<uint16_t>(mull(x, y) >> 16);
#endif
}
SINT T dot(const Vec<N, T>& a, const Vec<N, T>& 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<N, float>& v) {
return std::sqrt(dot(v, v));
}
SIN double length(const Vec<N, double>& v) {
return std::sqrt(dot(v, v));
}
SIN Vec<N, float> normalize(const Vec<N, float>& v) {
return v / length(v);
}
SIN Vec<N, double> normalize(const Vec<N, double>& v) {
return v / length(v);
}
SINT bool isfinite(const Vec<N, T>& 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<N, T>(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<N,T>& a,
Vec<N,T>& b,
Vec<N,T>& c,
Vec<N,T>& 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<N,T>& a, \
Vec<N,T>& b, \
Vec<N,T>& c, \
Vec<N,T>& d) { \
auto mat = VLD(v); \
a = sk_bit_cast<Vec<N,T>>(mat.val[0]); \
b = sk_bit_cast<Vec<N,T>>(mat.val[1]); \
c = sk_bit_cast<Vec<N,T>>(mat.val[2]); \
d = sk_bit_cast<Vec<N,T>>(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<Vec<4,float>>(a_);
b = sk_bit_cast<Vec<4,float>>(b_);
c = sk_bit_cast<Vec<4,float>>(c_);
d = sk_bit_cast<Vec<4,float>>(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<N,T>& a, Vec<N,T>& 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<N,T>& a, Vec<N,T>& b) { \
auto mat = VLD(v); \
a = sk_bit_cast<Vec<N,T>>(mat.val[0]); \
b = sk_bit_cast<Vec<N,T>>(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<kPointCount, SkPoint> 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<SkData> 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<int>(SkPathFillType::kWinding), "fill_type_mismatch");
static_assert(1 == static_cast<int>(SkPathFillType::kEvenOdd), "fill_type_mismatch");
static_assert(2 == static_cast<int>(SkPathFillType::kInverseWinding), "fill_type_mismatch");
static_assert(3 == static_cast<int>(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<SkIDChangeListener> 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<SkPath::Verb>(*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<int>(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<int>(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 <unsigned N> 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<SkString>* out);
inline void SkStrSplit(
const char* str, const char* delimiters, skia_private::TArray<SkString>* 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<int16_t>(r.fLeft) && SkTFitsIn<int16_t>(r.fTop) &&
SkTFitsIn<int16_t>(r.fRight) && SkTFitsIn<int16_t>(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<CoinDictEntry> 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<SkOpSpanBase*>& , 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<SkTCubic>(); }
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<SkTQuad>(); }
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<SkTConic>(); }
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<SkOpSpanBase*>(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 <typename T> 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<SkOpAngle>();
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<SkOpAngle>();
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<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart,
SkOpSpanBase** nextEnd, bool* unsortable, bool* simple,
SkPathOp op, int xorMiMask, int xorSuMask);
SkOpSegment* findNextWinding(SkTDArray<SkOpSpanBase*>* 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<SkOpSpan>();
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<SkOpSegment>()
: &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<SkOpContour>();
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 <typename T> 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<SkOpPtT*>(fCoinPtTEnd); }
SkOpPtT* coinPtTStartWritable() const { return const_cast<SkOpPtT*>(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<SkOpPtT*>(fOppPtTStart); }
SkOpPtT* oppPtTEndWritable() const { return const_cast<SkOpPtT*>(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<SkOpPtT*>(coinPtTStart), const_cast<SkOpPtT*>(coinPtTEnd),
const_cast<SkOpPtT*>(oppPtTStart), const_cast<SkOpPtT*>(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<SkCoincidentSpans*>* 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<const SkOpPtT*>& endPtTs() const { return fEndPtTs; }
void lineTo();
bool matchedLast(const SkOpPtT*) const;
void moveTo();
const skia_private::TArray<SkPath>& partials() const { return fPartials; }
bool someAssemblyRequired();
SkPoint update(const SkOpPtT* pt);
SkPath fCurrent; // contour under construction
skia_private::TArray<SkPath> fPartials; // contours with mismatched starts and ends
SkTDArray<const SkOpPtT*> 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<SkPoint> fPathPts;
SkTDArray<SkScalar> fWeights;
SkTDArray<uint8_t> 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 <typename T> class SkTDArray;
const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr,
bool* sortable);
SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* 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<uint8_t>(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<uint32_t>::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<char*>(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<const uint32_t, 47> 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<double, 2> 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<double, 4> 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<double, 4> 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<const float>
SkBezierCubic::IntersectWithHorizontalLine(
SkSpan<const SkPoint> 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<const float>
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<double> 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<const float>
SkBezierQuad::IntersectWithHorizontalLine(SkSpan<const SkPoint> 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<const float> 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<intptr_t>(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<std::byte> 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<std::byte*>(ptr), completeSize};
}
} // namespace
SkSpan<std::byte> 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<int64_t>(capacity * growthFactor);
// Notice that for small values of capacity, rounding up will provide most of the growth.
return this->roundUpCapacity(capacityGrowth);
}
SkSpan<std::byte> 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<std::byte> 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<int>(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<double>::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<double>::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<float>::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<double>::infinity();
} else {
root = std::numeric_limits<double>::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<std::byte*>(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<std::byte*>(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<std::byte*>(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<int>(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 <typename T> static constexpr bool is_align2(T x) { return 0 == (x & 1); }
template <typename T> 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<int>(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 <typename T>
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> 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<SkData> data(new (storage) SkData(length));
if (srcOrNull) {
memcpy(data->writable_data(), srcOrNull, length);
}
return data;
}
void SkData::NoopReleaseProc(const void*, void*) {}
///////////////////////////////////////////////////////////////////////////////
sk_sp<SkData> 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> SkData::MakeFromMalloc(const void* data, size_t length) {
return sk_sp<SkData>(new SkData(data, length, sk_free_releaseproc, nullptr));
}
sk_sp<SkData> SkData::MakeWithCopy(const void* src, size_t length) {
SkASSERT(src);
return PrivateNewWithCopy(src, length);
}
sk_sp<SkData> SkData::MakeUninitialized(size_t length) {
return PrivateNewWithCopy(nullptr, length);
}
sk_sp<SkData> SkData::MakeZeroInitialized(size_t length) {
auto data = MakeUninitialized(length);
if (length != 0) {
memset(data->writable_data(), 0, data->size());
}
return data;
}
sk_sp<SkData> SkData::MakeWithProc(const void* ptr, size_t length, ReleaseProc proc, void* ctx) {
return sk_sp<SkData>(new SkData(ptr, length, proc, ctx));
}
// assumes context is a SkData
static void sk_dataref_releaseproc(const void*, void* context) {
SkData* src = reinterpret_cast<SkData*>(context);
src->unref();
}
sk_sp<SkData> 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<SkData>(new SkData(src->bytes() + offset, length, sk_dataref_releaseproc,
const_cast<SkData*>(src)));
}
sk_sp<SkData> 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> 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<SkData> 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<SkVector, 2> 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<int N, typename T>
inline static skvx::Vec<N,T> unchecked_mix(const skvx::Vec<N,T>& a, const skvx::Vec<N,T>& b,
const skvx::Vec<N,T>& 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<float2>(src[0]);
float2 p1 = sk_bit_cast<float2>(src[1]);
float2 p2 = sk_bit_cast<float2>(src[2]);
float2 p3 = sk_bit_cast<float2>(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<SkPoint>(p0);
dst[1] = sk_bit_cast<SkPoint>(ab);
dst[2] = sk_bit_cast<SkPoint>(abc);
dst[3] = sk_bit_cast<SkPoint>(abcd);
dst[4] = sk_bit_cast<SkPoint>(bcd);
dst[5] = sk_bit_cast<SkPoint>(cd);
dst[6] = sk_bit_cast<SkPoint>(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<float2>(src[0]);
p11.lo = p11.hi = sk_bit_cast<float2>(src[1]);
p22.lo = p22.hi = sk_bit_cast<float2>(src[2]);
p33.lo = p33.hi = sk_bit_cast<float2>(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<SkPoint>(p00.lo);
dst[1] = sk_bit_cast<SkPoint>(ab.lo);
dst[2] = sk_bit_cast<SkPoint>(abc.lo);
dst[3] = sk_bit_cast<SkPoint>(abcd.lo);
middle.store(dst + 4);
dst[6] = sk_bit_cast<SkPoint>(abcd.hi);
dst[7] = sk_bit_cast<SkPoint>(bcd.hi);
dst[8] = sk_bit_cast<SkPoint>(cd.hi);
dst[9] = sk_bit_cast<SkPoint>(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 <typename T> 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<SkIDChangeListener> 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 <typename T> 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 <x,y,x,y>, '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<SkPoint3*>(reinterpret_cast<char*>(dst) + dstStride);
src = reinterpret_cast<const SkPoint3*>(reinterpret_cast<const char*>(src) +
srcStride);
}
}
}
return;
}
do {
SkScalar sx = src->fX;
SkScalar sy = src->fY;
SkScalar sw = src->fZ;
src = reinterpret_cast<const SkPoint3*>(reinterpret_cast<const char*>(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<SkPoint3*>(reinterpret_cast<char*>(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 <MinMaxOrBoth MIN_MAX_OR_BOTH> 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<kMin_MinMaxOrBoth>(this->getType(), fMat, &factor)) {
return factor;
} else {
return -1;
}
}
SkScalar SkMatrix::getMaxScale() const {
SkScalar factor;
if (get_scale_factor<kMax_MinMaxOrBoth>(this->getType(), fMat, &factor)) {
return factor;
} else {
return -1;
}
}
bool SkMatrix::getMinMaxScales(SkScalar scaleFactors[2]) const {
return get_scale_factor<kBoth_MinMaxOrBoth>(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<SkPathFirstDirection>(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<SkPathRef> 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(&currentPoint);
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<SkPath> 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<SkPath> 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 <size_t N> 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<SkVector>* 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<SkVector>* 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<SkVector>* 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<SkVector>* 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<SkVector> 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<SkPathRef>(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<SkPathRef> 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<SkPathRef>(new SkPathRef(fPts,
fVerbs,
fConicWeights,
fSegmentMask)));
}
SkPath SkPathBuilder::detach() {
auto path = this->make(sk_sp<SkPathRef>(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 <unsigned N> 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<SkPathRef>* 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<SkPathRef>* dst,
const SkPathRef& src,
const SkMatrix& matrix) {
SkDEBUGCODE(src.validate();)
if (matrix.isIdentity()) {
if (dst->get() != &src) {
src.ref();
dst->reset(const_cast<SkPathRef*>(&src));
SkDEBUGCODE((*dst)->validate();)
}
return;
}
sk_sp<const SkPathRef> 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<SkPathRef>* 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<SkPoint*, SkScalar*> 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<int64_t>(1) << kPathRefGenIDBitCnt) - 1;
if (fGenerationID == 0) {
if (fPoints.empty() && fVerbs.empty()) {
fGenerationID = kEmptyGenID;
} else {
static std::atomic<uint32_t> 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<uint32_t>(fillType) << kPathRefGenIDBitCnt;
#endif
return fGenerationID;
}
void SkPathRef::addGenIDChangeListener(sk_sp<SkIDChangeListener> 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 use_rsqrt> 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<false>(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<false>(this, x, y, length);
}
bool SkPointPriv::SetLengthFast(SkPoint* pt, float length) {
return set_point_length<true>(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<typename R>
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<SkRect>(a, b, out);
}
bool SkRectPriv::Subtract(const SkIRect& a, const SkIRect& b, SkIRect* out) {
return subtract<SkIRect>(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<SkData> 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<SkData> data) : fData(std::move(data)) {
if (nullptr == fData) {
fData = SkData::MakeEmpty();
}
fOffset = 0;
}
std::unique_ptr<SkMemoryStream> SkMemoryStream::MakeCopy(const void* data, size_t length) {
return std::make_unique<SkMemoryStream>(data, length, true);
}
std::unique_ptr<SkMemoryStream> SkMemoryStream::MakeDirect(const void* data, size_t length) {
return std::make_unique<SkMemoryStream>(data, length, false);
}
std::unique_ptr<SkMemoryStream> SkMemoryStream::Make(sk_sp<SkData> data) {
return std::make_unique<SkMemoryStream>(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<SkData> 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<SkMemoryStream*>(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<SkMemoryStream> 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<size_t>(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<char*>(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<SkData> SkDynamicMemoryWStream::detachAsData() {
const size_t size = this->bytesWritten();
if (0 == size) {
return SkData::MakeEmpty();
}
sk_sp<SkData> 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<SkBlockMemoryRefCnt> 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<void>(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<char*>(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<SkBlockMemoryRefCnt> const fBlockMemory;
SkDynamicMemoryWStream::Block const * fCurrent;
size_t const fSize;
size_t fOffset;
size_t fCurrentOffset;
};
std::unique_ptr<SkStreamAsset> 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<SkStreamAsset> stream
= std::make_unique<SkBlockMemoryStream>(sk_make_sp<SkBlockMemoryRefCnt>(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<SkData> 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<const char*>(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 <int SIZE>
static StringBuffer apply_format_string(const char* format, va_list args, char (&stackBuffer)[SIZE],
SkString* heapBuffer) SK_PRINTF_LIKE(1, 0);
template <int SIZE>
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> SkString::Rec::Make(const char text[], size_t len) {
if (0 == len) {
return sk_sp<SkString::Rec>(const_cast<Rec*>(&gEmptyRec));
}
SkSafeMath safe;
// We store a 32bit version of the length
uint32_t stringLen = safe.castTo<uint32_t>(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> 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<const SkString*>(this)->validate();
return *this;
}
#endif
///////////////////////////////////////////////////////////////////////////////
SkString::SkString() : fRec(const_cast<Rec*>(&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<Rec*>(&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<Rec>::operator=(const sk_sp<Ref>&) 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<Rec*>(&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<uint32_t>(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<SkString>* 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, &ltOrder);
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<double>(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<SkCoincidentSpans>();
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<SkOpSegment*>(testSeg);
SkOpPtT* oppStart = writableSeg->addT(t);
if (oppStart == testPtT) {
continue;
}
SkOpSpan* writableBase = const_cast<SkOpSpan*>(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<SkCoincidentSpans*>* 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<SkCoincidentSpans*> 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<SkOpPtT*>(cs)
: coinSeg->addT(coinTs);
if (csWritable == ce) {
return true;
}
SkOpPtT* osWritable = os ? const_cast<SkOpPtT*>(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<SkOpPtT*>(ce)
: coinSeg->addT(coinTe);
SkOpPtT* oeWritable = oe ? const_cast<SkOpPtT*>(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<SkOpSegment*>(outerCoin);
SkOpSegment* outerOppWritable = const_cast<SkOpSegment*>(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<SkOpSegment*>(innerCoin);
SkOpSegment* innerOppWritable = const_cast<SkOpSegment*>(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<SkPoint>(3);
memcpy(ptStorage, pts, sizeof(SkPoint) * 3);
this->addQuad(ptStorage);
} break;
case SkPath::kConic_Verb: {
SkPoint* ptStorage = allocator->makeArrayDefault<SkPoint>(3);
memcpy(ptStorage, pts, sizeof(SkPoint) * 3);
this->addConic(ptStorage, weight);
} break;
case SkPath::kCubic_Verb: {
SkPoint* ptStorage = allocator->makeArrayDefault<SkPoint>(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<SkPoint>(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<SkPath::Verb>(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<SkOpSpan*>(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<SkOpSpanBase*>(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<SkOpAngle>();
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<SkOpAngle>();
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<SkOpSpanBase*>* 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<SkOpSpanBase*>* 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<SkOpSpan*>(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<SkOpSpanBase*>* 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<SkOpContour* > 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<SkOpContour>(list.begin(), list.end());
}
contour = list[0];
SkOpContourHead* contourHead = static_cast<SkOpContourHead*>(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<SkScalar>((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<int>(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<SkOpSpanBase*>& 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<SpanGlitch> 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<SkOpGlobalState*>(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<const SkOpAngle*> 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<SkOpSegment*>(testSeg);
SkOpPtT* oppStart = writableSeg->addT(t);
if (oppStart == testPtT) {
continue;
}
SkOpSpan* writableBase = const_cast<SkOpSpan*>(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<SkCoincidentSpans*> 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<SkOpSegment*>(outerCoin);
// SkOpSegment* outerOppWritable = const_cast<SkOpSegment*>(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<SkOpSegment*>(innerCoin);
// SkOpSegment* innerOppWritable = const_cast<SkOpSegment*>(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<SkOpSpan*>(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<SkOpPtT*>(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<SkRect> rects;
SkTDArray<SkPathDirection> 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<SkData> 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<SkOpSpanBase*>& 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<SkOpSpanBase*> 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, &current)) {
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<SkOpContourHead*>(&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<SkOpSpanBase*> 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<SkOpContourHead*>(&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<SkTSpanBounded>();
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<SkTSpan>(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<typename SkTCurve>
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<SkDCubic::kMaxIntersections * 3,
const SkClosestRecord*, true> closestPtrs;
for (int index = 0; index < fUsed; ++index) {
closestPtrs.push_back(&fClosest[index]);
}
SkTQSort<const SkClosestRecord>(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<SkDCubic::kMaxIntersections * 2, SkClosestRecord, true> 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, &sect1->fHeap);
span2->addBounded(span1, &sect2->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, &sect1->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, &sect2->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(&sect1, &sect2, 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(&sect1, &sect2, 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(&sect1, &sect2, 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(&sect1, &sect2, 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(&sect1, &sect2, 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(&sect1, &sect2, 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<int>(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<int>(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<bool>((static_cast<int>(dir) & 2) == 0);
}
static bool ccw_dxdy(const SkDVector& v, SkOpRayDir dir) {
bool vPartPos = pt_dydx(v, dir) > 0;
bool leftBottom = ((static_cast<int>(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<SkOpRayHit>();
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<SkOpRayDir>(static_cast<int>(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<int>(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<SkOpPtT*>(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<SkOpPtT**>(&runs[pIndex]);
*runsPtr = opPtT;
} while (true);
partWriter.finishContour();
const TArray<SkPath>& partPartials = partWriter.partials();
if (partPartials.empty()) {
continue;
}
// if pIndex is even, reverse and prepend to fPartials; otherwise, append
SkPath& partial = const_cast<SkPath&>(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<int> 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<int>(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<int>::max();
if (*str == '-')
{
sign = -1;
maxAbsValue = -static_cast<int64_t>(std::numeric_limits<int>::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