54184 lines
1.8 MiB
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(¤tPoint);
|
|
return this->arcTo(rx, ry, xAxisRotate, largeArc, sweep,
|
|
currentPoint.fX + dx, currentPoint.fY + dy);
|
|
}
|
|
|
|
SkPath& SkPath::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
|
|
if (oval.isEmpty() || 0 == sweepAngle) {
|
|
return *this;
|
|
}
|
|
|
|
const SkScalar kFullCircleAngle = SkIntToScalar(360);
|
|
|
|
if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
|
|
// We can treat the arc as an oval if it begins at one of our legal starting positions.
|
|
// See SkPath::addOval() docs.
|
|
SkScalar startOver90 = startAngle / 90.f;
|
|
SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
|
|
SkScalar error = startOver90 - startOver90I;
|
|
if (SkScalarNearlyEqual(error, 0)) {
|
|
// Index 1 is at startAngle == 0.
|
|
SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
|
|
startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
|
|
return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW,
|
|
(unsigned) startIndex);
|
|
}
|
|
}
|
|
return this->arcTo(oval, startAngle, sweepAngle, true);
|
|
}
|
|
|
|
/*
|
|
Need to handle the case when the angle is sharp, and our computed end-points
|
|
for the arc go behind pt1 and/or p2...
|
|
*/
|
|
SkPath& SkPath::arcTo(SkScalar x1, SkScalar y1, SkScalar x2, SkScalar y2, SkScalar radius) {
|
|
this->injectMoveToIfNeeded();
|
|
|
|
if (radius == 0) {
|
|
return this->lineTo(x1, y1);
|
|
}
|
|
|
|
// need to know our prev pt so we can construct tangent vectors
|
|
SkPoint start;
|
|
this->getLastPt(&start);
|
|
|
|
// need double precision for these calcs.
|
|
skvx::double2 befored = normalize(skvx::double2{x1 - start.fX, y1 - start.fY});
|
|
skvx::double2 afterd = normalize(skvx::double2{x2 - x1, y2 - y1});
|
|
double cosh = dot(befored, afterd);
|
|
double sinh = cross(befored, afterd);
|
|
|
|
// If the previous point equals the first point, befored will be denormalized.
|
|
// If the two points equal, afterd will be denormalized.
|
|
// If the second point equals the first point, sinh will be zero.
|
|
// In all these cases, we cannot construct an arc, so we construct a line to the first point.
|
|
if (!isfinite(befored) || !isfinite(afterd) || SkScalarNearlyZero(SkDoubleToScalar(sinh))) {
|
|
return this->lineTo(x1, y1);
|
|
}
|
|
|
|
// safe to convert back to floats now
|
|
SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh));
|
|
SkScalar xx = x1 - dist * befored[0];
|
|
SkScalar yy = y1 - dist * befored[1];
|
|
|
|
SkVector after = SkVector::Make(afterd[0], afterd[1]);
|
|
after.setLength(dist);
|
|
this->lineTo(xx, yy);
|
|
SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5));
|
|
return this->conicTo(x1, y1, x1 + after.fX, y1 + after.fY, weight);
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
SkPath& SkPath::addPath(const SkPath& path, SkScalar dx, SkScalar dy, AddPathMode mode) {
|
|
SkMatrix matrix;
|
|
|
|
matrix.setTranslate(dx, dy);
|
|
return this->addPath(path, matrix, mode);
|
|
}
|
|
|
|
SkPath& SkPath::addPath(const SkPath& srcPath, const SkMatrix& matrix, AddPathMode mode) {
|
|
if (srcPath.isEmpty()) {
|
|
return *this;
|
|
}
|
|
|
|
// Detect if we're trying to add ourself
|
|
const SkPath* src = &srcPath;
|
|
SkTLazy<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, <Order);
|
|
this->alignmentSameSide(rh, &trOrder);
|
|
if (lrOrder >= 0 && ltOrder >= 0 && trOrder >= 0) {
|
|
return COMPARE_RESULT(7, lrOrder ? (ltOrder & trOrder) : (ltOrder | trOrder));
|
|
}
|
|
// SkASSERT(lrOrder >= 0 || ltOrder >= 0 || trOrder >= 0);
|
|
// There's not enough information to sort. Get the pairs of angles in opposite planes.
|
|
// If an order is < 0, the pair is already in an opposite plane. Check the remaining pairs.
|
|
// FIXME : once all variants are understood, rewrite this more simply
|
|
if (ltOrder == 0 && lrOrder == 0) {
|
|
SkASSERT(trOrder < 0);
|
|
// FIXME : once this is verified to work, remove one opposite angle call
|
|
SkDEBUGCODE(bool lrOpposite = lh->oppositePlanes(rh));
|
|
bool ltOpposite = lh->oppositePlanes(this);
|
|
SkOPASSERT(lrOpposite != ltOpposite);
|
|
return COMPARE_RESULT(8, ltOpposite);
|
|
} else if (ltOrder == 1 && trOrder == 0) {
|
|
SkASSERT(lrOrder < 0);
|
|
bool trOpposite = oppositePlanes(rh);
|
|
return COMPARE_RESULT(9, trOpposite);
|
|
} else if (lrOrder == 1 && trOrder == 1) {
|
|
SkASSERT(ltOrder < 0);
|
|
// SkDEBUGCODE(bool trOpposite = oppositePlanes(rh));
|
|
bool lrOpposite = lh->oppositePlanes(rh);
|
|
// SkASSERT(lrOpposite != trOpposite);
|
|
return COMPARE_RESULT(10, lrOpposite);
|
|
}
|
|
// If a pair couldn't be ordered, there's not enough information to determine the sort.
|
|
// Refer to: https://docs.google.com/drawings/d/1KV-8SJTedku9fj4K6fd1SB-8divuV_uivHVsSgwXICQ
|
|
if (fUnorderable || lh->fUnorderable || rh->fUnorderable) {
|
|
// limit to lines; should work with curves, but wait for a failing test to verify
|
|
if (!fPart.isCurve() && !lh->fPart.isCurve() && !rh->fPart.isCurve()) {
|
|
// see if original raw data is orderable
|
|
// if two share a point, check if third has both points in same half plane
|
|
int ltShare = lh->fOriginalCurvePart[0] == fOriginalCurvePart[0];
|
|
int lrShare = lh->fOriginalCurvePart[0] == rh->fOriginalCurvePart[0];
|
|
int trShare = fOriginalCurvePart[0] == rh->fOriginalCurvePart[0];
|
|
// if only one pair are the same, the third point touches neither of the pair
|
|
if (ltShare + lrShare + trShare == 1) {
|
|
if (lrShare) {
|
|
int ltOOrder = lh->linesOnOriginalSide(this);
|
|
int rtOOrder = rh->linesOnOriginalSide(this);
|
|
if ((rtOOrder ^ ltOOrder) == 1) {
|
|
return ltOOrder;
|
|
}
|
|
} else if (trShare) {
|
|
int tlOOrder = this->linesOnOriginalSide(lh);
|
|
int rlOOrder = rh->linesOnOriginalSide(lh);
|
|
if ((tlOOrder ^ rlOOrder) == 1) {
|
|
return rlOOrder;
|
|
}
|
|
} else {
|
|
SkASSERT(ltShare);
|
|
int trOOrder = rh->linesOnOriginalSide(this);
|
|
int lrOOrder = lh->linesOnOriginalSide(rh);
|
|
// result must be 0 and 1 or 1 and 0 to be valid
|
|
if ((lrOOrder ^ trOOrder) == 1) {
|
|
return trOOrder;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (lrOrder < 0) {
|
|
if (ltOrder < 0) {
|
|
return COMPARE_RESULT(11, trOrder);
|
|
}
|
|
return COMPARE_RESULT(12, ltOrder);
|
|
}
|
|
return COMPARE_RESULT(13, !lrOrder);
|
|
}
|
|
|
|
int SkOpAngle::lineOnOneSide(const SkDPoint& origin, const SkDVector& line, const SkOpAngle* test,
|
|
bool useOriginal) const {
|
|
double crosses[3];
|
|
SkPath::Verb testVerb = test->segment()->verb();
|
|
int iMax = SkPathOpsVerbToPoints(testVerb);
|
|
// SkASSERT(origin == test.fCurveHalf[0]);
|
|
const SkDCurve& testCurve = useOriginal ? test->fOriginalCurvePart : test->fPart.fCurve;
|
|
for (int index = 1; index <= iMax; ++index) {
|
|
double xy1 = line.fX * (testCurve[index].fY - origin.fY);
|
|
double xy2 = line.fY * (testCurve[index].fX - origin.fX);
|
|
crosses[index - 1] = AlmostBequalUlps(xy1, xy2) ? 0 : xy1 - xy2;
|
|
}
|
|
if (crosses[0] * crosses[1] < 0) {
|
|
return -1;
|
|
}
|
|
if (SkPath::kCubic_Verb == testVerb) {
|
|
if (crosses[0] * crosses[2] < 0 || crosses[1] * crosses[2] < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
if (crosses[0]) {
|
|
return crosses[0] < 0;
|
|
}
|
|
if (crosses[1]) {
|
|
return crosses[1] < 0;
|
|
}
|
|
if (SkPath::kCubic_Verb == testVerb && crosses[2]) {
|
|
return crosses[2] < 0;
|
|
}
|
|
return -2;
|
|
}
|
|
|
|
// given a line, see if the opposite curve's convex hull is all on one side
|
|
// returns -1=not on one side 0=this CW of test 1=this CCW of test
|
|
int SkOpAngle::lineOnOneSide(const SkOpAngle* test, bool useOriginal) {
|
|
SkASSERT(!fPart.isCurve());
|
|
SkASSERT(test->fPart.isCurve());
|
|
SkDPoint origin = fPart.fCurve[0];
|
|
SkDVector line = fPart.fCurve[1] - origin;
|
|
int result = this->lineOnOneSide(origin, line, test, useOriginal);
|
|
if (-2 == result) {
|
|
fUnorderable = true;
|
|
result = -1;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// experiment works only with lines for now
|
|
int SkOpAngle::linesOnOriginalSide(const SkOpAngle* test) {
|
|
SkASSERT(!fPart.isCurve());
|
|
SkASSERT(!test->fPart.isCurve());
|
|
SkDPoint origin = fOriginalCurvePart[0];
|
|
SkDVector line = fOriginalCurvePart[1] - origin;
|
|
double dots[2];
|
|
double crosses[2];
|
|
const SkDCurve& testCurve = test->fOriginalCurvePart;
|
|
for (int index = 0; index < 2; ++index) {
|
|
SkDVector testLine = testCurve[index] - origin;
|
|
double xy1 = line.fX * testLine.fY;
|
|
double xy2 = line.fY * testLine.fX;
|
|
dots[index] = line.fX * testLine.fX + line.fY * testLine.fY;
|
|
crosses[index] = AlmostBequalUlps(xy1, xy2) ? 0 : xy1 - xy2;
|
|
}
|
|
if (crosses[0] * crosses[1] < 0) {
|
|
return -1;
|
|
}
|
|
if (crosses[0]) {
|
|
return crosses[0] < 0;
|
|
}
|
|
if (crosses[1]) {
|
|
return crosses[1] < 0;
|
|
}
|
|
if ((!dots[0] && dots[1] < 0) || (dots[0] < 0 && !dots[1])) {
|
|
return 2; // 180 degrees apart
|
|
}
|
|
fUnorderable = true;
|
|
return -1;
|
|
}
|
|
|
|
// To sort the angles, all curves are translated to have the same starting point.
|
|
// If the curve's control point in its original position is on one side of a compared line,
|
|
// and translated is on the opposite side, reverse the previously computed order.
|
|
void SkOpAngle::alignmentSameSide(const SkOpAngle* test, int* order) const {
|
|
if (*order < 0) {
|
|
return;
|
|
}
|
|
if (fPart.isCurve()) {
|
|
// This should support all curve types, but only bug that requires this has lines
|
|
// Turning on for curves causes existing tests to fail
|
|
return;
|
|
}
|
|
if (test->fPart.isCurve()) {
|
|
return;
|
|
}
|
|
const SkDPoint& xOrigin = test->fPart.fCurve.fLine[0];
|
|
const SkDPoint& oOrigin = test->fOriginalCurvePart.fLine[0];
|
|
if (xOrigin == oOrigin) {
|
|
return;
|
|
}
|
|
int iMax = SkPathOpsVerbToPoints(this->segment()->verb());
|
|
SkDVector xLine = test->fPart.fCurve.fLine[1] - xOrigin;
|
|
SkDVector oLine = test->fOriginalCurvePart.fLine[1] - oOrigin;
|
|
for (int index = 1; index <= iMax; ++index) {
|
|
const SkDPoint& testPt = fPart.fCurve[index];
|
|
double xCross = oLine.crossCheck(testPt - xOrigin);
|
|
double oCross = xLine.crossCheck(testPt - oOrigin);
|
|
if (oCross * xCross < 0) {
|
|
*order ^= 1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool SkOpAngle::checkCrossesZero() const {
|
|
int start = std::min(fSectorStart, fSectorEnd);
|
|
int end = std::max(fSectorStart, fSectorEnd);
|
|
bool crossesZero = end - start > 16;
|
|
return crossesZero;
|
|
}
|
|
|
|
bool SkOpAngle::checkParallel(SkOpAngle* rh) {
|
|
SkDVector scratch[2];
|
|
const SkDVector* sweep, * tweep;
|
|
if (this->fPart.isOrdered()) {
|
|
sweep = this->fPart.fSweep;
|
|
} else {
|
|
scratch[0] = this->fPart.fCurve[1] - this->fPart.fCurve[0];
|
|
sweep = &scratch[0];
|
|
}
|
|
if (rh->fPart.isOrdered()) {
|
|
tweep = rh->fPart.fSweep;
|
|
} else {
|
|
scratch[1] = rh->fPart.fCurve[1] - rh->fPart.fCurve[0];
|
|
tweep = &scratch[1];
|
|
}
|
|
double s0xt0 = sweep->crossCheck(*tweep);
|
|
if (tangentsDiverge(rh, s0xt0)) {
|
|
return s0xt0 < 0;
|
|
}
|
|
// compute the perpendicular to the endpoints and see where it intersects the opposite curve
|
|
// if the intersections within the t range, do a cross check on those
|
|
bool inside;
|
|
if (!fEnd->contains(rh->fEnd)) {
|
|
if (this->endToSide(rh, &inside)) {
|
|
return inside;
|
|
}
|
|
if (rh->endToSide(this, &inside)) {
|
|
return !inside;
|
|
}
|
|
}
|
|
if (this->midToSide(rh, &inside)) {
|
|
return inside;
|
|
}
|
|
if (rh->midToSide(this, &inside)) {
|
|
return !inside;
|
|
}
|
|
// compute the cross check from the mid T values (last resort)
|
|
SkDVector m0 = segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0];
|
|
SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0];
|
|
double m0xm1 = m0.crossCheck(m1);
|
|
if (m0xm1 == 0) {
|
|
this->fUnorderable = true;
|
|
rh->fUnorderable = true;
|
|
return true;
|
|
}
|
|
return m0xm1 < 0;
|
|
}
|
|
|
|
// the original angle is too short to get meaningful sector information
|
|
// lengthen it until it is long enough to be meaningful or leave it unset if lengthening it
|
|
// would cause it to intersect one of the adjacent angles
|
|
bool SkOpAngle::computeSector() {
|
|
if (fComputedSector) {
|
|
return !fUnorderable;
|
|
}
|
|
fComputedSector = true;
|
|
bool stepUp = fStart->t() < fEnd->t();
|
|
SkOpSpanBase* checkEnd = fEnd;
|
|
if (checkEnd->final() && stepUp) {
|
|
fUnorderable = true;
|
|
return false;
|
|
}
|
|
do {
|
|
// advance end
|
|
const SkOpSegment* other = checkEnd->segment();
|
|
const SkOpSpanBase* oSpan = other->head();
|
|
do {
|
|
if (oSpan->segment() != segment()) {
|
|
continue;
|
|
}
|
|
if (oSpan == checkEnd) {
|
|
continue;
|
|
}
|
|
if (!approximately_equal(oSpan->t(), checkEnd->t())) {
|
|
continue;
|
|
}
|
|
goto recomputeSector;
|
|
} while (!oSpan->final() && (oSpan = oSpan->upCast()->next()));
|
|
checkEnd = stepUp ? !checkEnd->final()
|
|
? checkEnd->upCast()->next() : nullptr
|
|
: checkEnd->prev();
|
|
} while (checkEnd);
|
|
recomputeSector:
|
|
SkOpSpanBase* computedEnd = stepUp ? checkEnd ? checkEnd->prev() : fEnd->segment()->head()
|
|
: checkEnd ? checkEnd->upCast()->next() : fEnd->segment()->tail();
|
|
if (checkEnd == fEnd || computedEnd == fEnd || computedEnd == fStart) {
|
|
fUnorderable = true;
|
|
return false;
|
|
}
|
|
if (stepUp != (fStart->t() < computedEnd->t())) {
|
|
fUnorderable = true;
|
|
return false;
|
|
}
|
|
SkOpSpanBase* saveEnd = fEnd;
|
|
fComputedEnd = fEnd = computedEnd;
|
|
setSpans();
|
|
setSector();
|
|
fEnd = saveEnd;
|
|
return !fUnorderable;
|
|
}
|
|
|
|
int SkOpAngle::convexHullOverlaps(const SkOpAngle* rh) {
|
|
const SkDVector* sweep = this->fPart.fSweep;
|
|
const SkDVector* tweep = rh->fPart.fSweep;
|
|
double s0xs1 = sweep[0].crossCheck(sweep[1]);
|
|
double s0xt0 = sweep[0].crossCheck(tweep[0]);
|
|
double s1xt0 = sweep[1].crossCheck(tweep[0]);
|
|
bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0;
|
|
double s0xt1 = sweep[0].crossCheck(tweep[1]);
|
|
double s1xt1 = sweep[1].crossCheck(tweep[1]);
|
|
tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0;
|
|
double t0xt1 = tweep[0].crossCheck(tweep[1]);
|
|
if (tBetweenS) {
|
|
return -1;
|
|
}
|
|
if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1
|
|
return -1;
|
|
}
|
|
bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0;
|
|
sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0;
|
|
if (sBetweenT) {
|
|
return -1;
|
|
}
|
|
// if all of the sweeps are in the same half plane, then the order of any pair is enough
|
|
if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) {
|
|
return 0;
|
|
}
|
|
if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) {
|
|
return 1;
|
|
}
|
|
// if the outside sweeps are greater than 180 degress:
|
|
// first assume the inital tangents are the ordering
|
|
// if the midpoint direction matches the inital order, that is enough
|
|
SkDVector m0 = this->segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0];
|
|
SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0];
|
|
double m0xm1 = m0.crossCheck(m1);
|
|
if (s0xt0 > 0 && m0xm1 > 0) {
|
|
return 0;
|
|
}
|
|
if (s0xt0 < 0 && m0xm1 < 0) {
|
|
return 1;
|
|
}
|
|
if (tangentsDiverge(rh, s0xt0)) {
|
|
return s0xt0 < 0;
|
|
}
|
|
return m0xm1 < 0;
|
|
}
|
|
|
|
// OPTIMIZATION: longest can all be either lazily computed here or precomputed in setup
|
|
double SkOpAngle::distEndRatio(double dist) const {
|
|
double longest = 0;
|
|
const SkOpSegment& segment = *this->segment();
|
|
int ptCount = SkPathOpsVerbToPoints(segment.verb());
|
|
const SkPoint* pts = segment.pts();
|
|
for (int idx1 = 0; idx1 <= ptCount - 1; ++idx1) {
|
|
for (int idx2 = idx1 + 1; idx2 <= ptCount; ++idx2) {
|
|
if (idx1 == idx2) {
|
|
continue;
|
|
}
|
|
SkDVector v;
|
|
v.set(pts[idx2] - pts[idx1]);
|
|
double lenSq = v.lengthSquared();
|
|
longest = std::max(longest, lenSq);
|
|
}
|
|
}
|
|
return sqrt(longest) / dist;
|
|
}
|
|
|
|
bool SkOpAngle::endsIntersect(SkOpAngle* rh) {
|
|
SkPath::Verb lVerb = this->segment()->verb();
|
|
SkPath::Verb rVerb = rh->segment()->verb();
|
|
int lPts = SkPathOpsVerbToPoints(lVerb);
|
|
int rPts = SkPathOpsVerbToPoints(rVerb);
|
|
SkDLine rays[] = {{{this->fPart.fCurve[0], rh->fPart.fCurve[rPts]}},
|
|
{{this->fPart.fCurve[0], this->fPart.fCurve[lPts]}}};
|
|
if (this->fEnd->contains(rh->fEnd)) {
|
|
return checkParallel(rh);
|
|
}
|
|
double smallTs[2] = {-1, -1};
|
|
bool limited[2] = {false, false};
|
|
for (int index = 0; index < 2; ++index) {
|
|
SkPath::Verb cVerb = index ? rVerb : lVerb;
|
|
// if the curve is a line, then the line and the ray intersect only at their crossing
|
|
if (cVerb == SkPath::kLine_Verb) {
|
|
continue;
|
|
}
|
|
const SkOpSegment& segment = index ? *rh->segment() : *this->segment();
|
|
SkIntersections i;
|
|
(*CurveIntersectRay[cVerb])(segment.pts(), segment.weight(), rays[index], &i);
|
|
double tStart = index ? rh->fStart->t() : this->fStart->t();
|
|
double tEnd = index ? rh->fComputedEnd->t() : this->fComputedEnd->t();
|
|
bool testAscends = tStart < (index ? rh->fComputedEnd->t() : this->fComputedEnd->t());
|
|
double t = testAscends ? 0 : 1;
|
|
for (int idx2 = 0; idx2 < i.used(); ++idx2) {
|
|
double testT = i[0][idx2];
|
|
if (!approximately_between_orderable(tStart, testT, tEnd)) {
|
|
continue;
|
|
}
|
|
if (approximately_equal_orderable(tStart, testT)) {
|
|
continue;
|
|
}
|
|
smallTs[index] = t = testAscends ? std::max(t, testT) : std::min(t, testT);
|
|
limited[index] = approximately_equal_orderable(t, tEnd);
|
|
}
|
|
}
|
|
bool sRayLonger = false;
|
|
SkDVector sCept = {0, 0};
|
|
double sCeptT = -1;
|
|
int sIndex = -1;
|
|
bool useIntersect = false;
|
|
for (int index = 0; index < 2; ++index) {
|
|
if (smallTs[index] < 0) {
|
|
continue;
|
|
}
|
|
const SkOpSegment& segment = index ? *rh->segment() : *this->segment();
|
|
const SkDPoint& dPt = segment.dPtAtT(smallTs[index]);
|
|
SkDVector cept = dPt - rays[index][0];
|
|
// If this point is on the curve, it should have been detected earlier by ordinary
|
|
// curve intersection. This may be hard to determine in general, but for lines,
|
|
// the point could be close to or equal to its end, but shouldn't be near the start.
|
|
if ((index ? lPts : rPts) == 1) {
|
|
SkDVector total = rays[index][1] - rays[index][0];
|
|
if (cept.lengthSquared() * 2 < total.lengthSquared()) {
|
|
continue;
|
|
}
|
|
}
|
|
SkDVector end = rays[index][1] - rays[index][0];
|
|
if (cept.fX * end.fX < 0 || cept.fY * end.fY < 0) {
|
|
continue;
|
|
}
|
|
double rayDist = cept.length();
|
|
double endDist = end.length();
|
|
bool rayLonger = rayDist > endDist;
|
|
if (limited[0] && limited[1] && rayLonger) {
|
|
useIntersect = true;
|
|
sRayLonger = rayLonger;
|
|
sCept = cept;
|
|
sCeptT = smallTs[index];
|
|
sIndex = index;
|
|
break;
|
|
}
|
|
double delta = fabs(rayDist - endDist);
|
|
double minX, minY, maxX, maxY;
|
|
minX = minY = SK_ScalarInfinity;
|
|
maxX = maxY = -SK_ScalarInfinity;
|
|
const SkDCurve& curve = index ? rh->fPart.fCurve : this->fPart.fCurve;
|
|
int ptCount = index ? rPts : lPts;
|
|
for (int idx2 = 0; idx2 <= ptCount; ++idx2) {
|
|
minX = std::min(minX, curve[idx2].fX);
|
|
minY = std::min(minY, curve[idx2].fY);
|
|
maxX = std::max(maxX, curve[idx2].fX);
|
|
maxY = std::max(maxY, curve[idx2].fY);
|
|
}
|
|
double maxWidth = std::max(maxX - minX, maxY - minY);
|
|
delta = sk_ieee_double_divide(delta, maxWidth);
|
|
// FIXME: move these magic numbers
|
|
// This fixes skbug.com/8380
|
|
// Larger changes (like changing the constant in the next block) cause other
|
|
// tests to fail as documented in the bug.
|
|
// This could probably become a more general test: e.g., if translating the
|
|
// curve causes the cross product of any control point or end point to change
|
|
// sign with regard to the opposite curve's hull, treat the curves as parallel.
|
|
|
|
// Moreso, this points to the general fragility of this approach of assigning
|
|
// winding by sorting the angles of curves sharing a common point, as mentioned
|
|
// in the bug.
|
|
if (delta < 4e-3 && delta > 1e-3 && !useIntersect && fPart.isCurve()
|
|
&& rh->fPart.isCurve() && fOriginalCurvePart[0] != fPart.fCurve.fLine[0]) {
|
|
// see if original curve is on one side of hull; translated is on the other
|
|
const SkDPoint& origin = rh->fOriginalCurvePart[0];
|
|
int count = SkPathOpsVerbToPoints(rh->segment()->verb());
|
|
const SkDVector line = rh->fOriginalCurvePart[count] - origin;
|
|
int originalSide = rh->lineOnOneSide(origin, line, this, true);
|
|
if (originalSide >= 0) {
|
|
int translatedSide = rh->lineOnOneSide(origin, line, this, false);
|
|
if (originalSide != translatedSide) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
if (delta > 1e-3 && (useIntersect ^= true)) {
|
|
sRayLonger = rayLonger;
|
|
sCept = cept;
|
|
sCeptT = smallTs[index];
|
|
sIndex = index;
|
|
}
|
|
}
|
|
if (useIntersect) {
|
|
const SkDCurve& curve = sIndex ? rh->fPart.fCurve : this->fPart.fCurve;
|
|
const SkOpSegment& segment = sIndex ? *rh->segment() : *this->segment();
|
|
double tStart = sIndex ? rh->fStart->t() : fStart->t();
|
|
SkDVector mid = segment.dPtAtT(tStart + (sCeptT - tStart) / 2) - curve[0];
|
|
double septDir = mid.crossCheck(sCept);
|
|
if (!septDir) {
|
|
return checkParallel(rh);
|
|
}
|
|
return sRayLonger ^ (sIndex == 0) ^ (septDir < 0);
|
|
} else {
|
|
return checkParallel(rh);
|
|
}
|
|
}
|
|
|
|
bool SkOpAngle::endToSide(const SkOpAngle* rh, bool* inside) const {
|
|
const SkOpSegment* segment = this->segment();
|
|
SkPath::Verb verb = segment->verb();
|
|
SkDLine rayEnd;
|
|
rayEnd[0].set(this->fEnd->pt());
|
|
rayEnd[1] = rayEnd[0];
|
|
SkDVector slopeAtEnd = (*CurveDSlopeAtT[verb])(segment->pts(), segment->weight(),
|
|
this->fEnd->t());
|
|
rayEnd[1].fX += slopeAtEnd.fY;
|
|
rayEnd[1].fY -= slopeAtEnd.fX;
|
|
SkIntersections iEnd;
|
|
const SkOpSegment* oppSegment = rh->segment();
|
|
SkPath::Verb oppVerb = oppSegment->verb();
|
|
(*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayEnd, &iEnd);
|
|
double endDist;
|
|
int closestEnd = iEnd.closestTo(rh->fStart->t(), rh->fEnd->t(), rayEnd[0], &endDist);
|
|
if (closestEnd < 0) {
|
|
return false;
|
|
}
|
|
if (!endDist) {
|
|
return false;
|
|
}
|
|
SkDPoint start;
|
|
start.set(this->fStart->pt());
|
|
// OPTIMIZATION: multiple times in the code we find the max scalar
|
|
double minX, minY, maxX, maxY;
|
|
minX = minY = SK_ScalarInfinity;
|
|
maxX = maxY = -SK_ScalarInfinity;
|
|
const SkDCurve& curve = rh->fPart.fCurve;
|
|
int oppPts = SkPathOpsVerbToPoints(oppVerb);
|
|
for (int idx2 = 0; idx2 <= oppPts; ++idx2) {
|
|
minX = std::min(minX, curve[idx2].fX);
|
|
minY = std::min(minY, curve[idx2].fY);
|
|
maxX = std::max(maxX, curve[idx2].fX);
|
|
maxY = std::max(maxY, curve[idx2].fY);
|
|
}
|
|
double maxWidth = std::max(maxX - minX, maxY - minY);
|
|
endDist = sk_ieee_double_divide(endDist, maxWidth);
|
|
if (!(endDist >= 5e-12)) { // empirically found
|
|
return false; // ! above catches NaN
|
|
}
|
|
const SkDPoint* endPt = &rayEnd[0];
|
|
SkDPoint oppPt = iEnd.pt(closestEnd);
|
|
SkDVector vLeft = *endPt - start;
|
|
SkDVector vRight = oppPt - start;
|
|
double dir = vLeft.crossNoNormalCheck(vRight);
|
|
if (!dir) {
|
|
return false;
|
|
}
|
|
*inside = dir < 0;
|
|
return true;
|
|
}
|
|
|
|
/* y<0 y==0 y>0 x<0 x==0 x>0 xy<0 xy==0 xy>0
|
|
0 x x x
|
|
1 x x x
|
|
2 x x x
|
|
3 x x x
|
|
4 x x x
|
|
5 x x x
|
|
6 x x x
|
|
7 x x x
|
|
8 x x x
|
|
9 x x x
|
|
10 x x x
|
|
11 x x x
|
|
12 x x x
|
|
13 x x x
|
|
14 x x x
|
|
15 x x x
|
|
*/
|
|
int SkOpAngle::findSector(SkPath::Verb verb, double x, double y) const {
|
|
double absX = fabs(x);
|
|
double absY = fabs(y);
|
|
double xy = SkPath::kLine_Verb == verb || !AlmostEqualUlps(absX, absY) ? absX - absY : 0;
|
|
// If there are four quadrants and eight octants, and since the Latin for sixteen is sedecim,
|
|
// one could coin the term sedecimant for a space divided into 16 sections.
|
|
// http://english.stackexchange.com/questions/133688/word-for-something-partitioned-into-16-parts
|
|
static const int sedecimant[3][3][3] = {
|
|
// y<0 y==0 y>0
|
|
// x<0 x==0 x>0 x<0 x==0 x>0 x<0 x==0 x>0
|
|
{{ 4, 3, 2}, { 7, -1, 15}, {10, 11, 12}}, // abs(x) < abs(y)
|
|
{{ 5, -1, 1}, {-1, -1, -1}, { 9, -1, 13}}, // abs(x) == abs(y)
|
|
{{ 6, 3, 0}, { 7, -1, 15}, { 8, 11, 14}}, // abs(x) > abs(y)
|
|
};
|
|
int sector = sedecimant[(xy >= 0) + (xy > 0)][(y >= 0) + (y > 0)][(x >= 0) + (x > 0)] * 2 + 1;
|
|
// SkASSERT(SkPath::kLine_Verb == verb || sector >= 0);
|
|
return sector;
|
|
}
|
|
|
|
SkOpGlobalState* SkOpAngle::globalState() const {
|
|
return this->segment()->globalState();
|
|
}
|
|
|
|
|
|
// OPTIMIZE: if this loops to only one other angle, after first compare fails, insert on other side
|
|
// OPTIMIZE: return where insertion succeeded. Then, start next insertion on opposite side
|
|
bool SkOpAngle::insert(SkOpAngle* angle) {
|
|
if (angle->fNext) {
|
|
if (loopCount() >= angle->loopCount()) {
|
|
if (!merge(angle)) {
|
|
return true;
|
|
}
|
|
} else if (fNext) {
|
|
if (!angle->merge(this)) {
|
|
return true;
|
|
}
|
|
} else {
|
|
angle->insert(this);
|
|
}
|
|
return true;
|
|
}
|
|
bool singleton = nullptr == fNext;
|
|
if (singleton) {
|
|
fNext = this;
|
|
}
|
|
SkOpAngle* next = fNext;
|
|
if (next->fNext == this) {
|
|
if (singleton || angle->after(this)) {
|
|
this->fNext = angle;
|
|
angle->fNext = next;
|
|
} else {
|
|
next->fNext = angle;
|
|
angle->fNext = this;
|
|
}
|
|
debugValidateNext();
|
|
return true;
|
|
}
|
|
SkOpAngle* last = this;
|
|
bool flipAmbiguity = false;
|
|
do {
|
|
SkASSERT(last->fNext == next);
|
|
if (angle->after(last) ^ (angle->tangentsAmbiguous() & flipAmbiguity)) {
|
|
last->fNext = angle;
|
|
angle->fNext = next;
|
|
debugValidateNext();
|
|
break;
|
|
}
|
|
last = next;
|
|
if (last == this) {
|
|
FAIL_IF(flipAmbiguity);
|
|
// We're in a loop. If a sort was ambiguous, flip it to end the loop.
|
|
flipAmbiguity = true;
|
|
}
|
|
next = next->fNext;
|
|
} while (true);
|
|
return true;
|
|
}
|
|
|
|
SkOpSpanBase* SkOpAngle::lastMarked() const {
|
|
if (fLastMarked) {
|
|
if (fLastMarked->chased()) {
|
|
return nullptr;
|
|
}
|
|
fLastMarked->setChased(true);
|
|
}
|
|
return fLastMarked;
|
|
}
|
|
|
|
bool SkOpAngle::loopContains(const SkOpAngle* angle) const {
|
|
if (!fNext) {
|
|
return false;
|
|
}
|
|
const SkOpAngle* first = this;
|
|
const SkOpAngle* loop = this;
|
|
const SkOpSegment* tSegment = angle->fStart->segment();
|
|
double tStart = angle->fStart->t();
|
|
double tEnd = angle->fEnd->t();
|
|
do {
|
|
const SkOpSegment* lSegment = loop->fStart->segment();
|
|
if (lSegment != tSegment) {
|
|
continue;
|
|
}
|
|
double lStart = loop->fStart->t();
|
|
if (lStart != tEnd) {
|
|
continue;
|
|
}
|
|
double lEnd = loop->fEnd->t();
|
|
if (lEnd == tStart) {
|
|
return true;
|
|
}
|
|
} while ((loop = loop->fNext) != first);
|
|
return false;
|
|
}
|
|
|
|
int SkOpAngle::loopCount() const {
|
|
int count = 0;
|
|
const SkOpAngle* first = this;
|
|
const SkOpAngle* next = this;
|
|
do {
|
|
next = next->fNext;
|
|
++count;
|
|
} while (next && next != first);
|
|
return count;
|
|
}
|
|
|
|
bool SkOpAngle::merge(SkOpAngle* angle) {
|
|
SkASSERT(fNext);
|
|
SkASSERT(angle->fNext);
|
|
SkOpAngle* working = angle;
|
|
do {
|
|
if (this == working) {
|
|
return false;
|
|
}
|
|
working = working->fNext;
|
|
} while (working != angle);
|
|
do {
|
|
SkOpAngle* next = working->fNext;
|
|
working->fNext = nullptr;
|
|
insert(working);
|
|
working = next;
|
|
} while (working != angle);
|
|
// it's likely that a pair of the angles are unorderable
|
|
debugValidateNext();
|
|
return true;
|
|
}
|
|
|
|
double SkOpAngle::midT() const {
|
|
return (fStart->t() + fEnd->t()) / 2;
|
|
}
|
|
|
|
bool SkOpAngle::midToSide(const SkOpAngle* rh, bool* inside) const {
|
|
const SkOpSegment* segment = this->segment();
|
|
SkPath::Verb verb = segment->verb();
|
|
const SkPoint& startPt = this->fStart->pt();
|
|
const SkPoint& endPt = this->fEnd->pt();
|
|
SkDPoint dStartPt;
|
|
dStartPt.set(startPt);
|
|
SkDLine rayMid;
|
|
rayMid[0].fX = (startPt.fX + endPt.fX) / 2;
|
|
rayMid[0].fY = (startPt.fY + endPt.fY) / 2;
|
|
rayMid[1].fX = rayMid[0].fX + (endPt.fY - startPt.fY);
|
|
rayMid[1].fY = rayMid[0].fY - (endPt.fX - startPt.fX);
|
|
SkIntersections iMid;
|
|
(*CurveIntersectRay[verb])(segment->pts(), segment->weight(), rayMid, &iMid);
|
|
int iOutside = iMid.mostOutside(this->fStart->t(), this->fEnd->t(), dStartPt);
|
|
if (iOutside < 0) {
|
|
return false;
|
|
}
|
|
const SkOpSegment* oppSegment = rh->segment();
|
|
SkPath::Verb oppVerb = oppSegment->verb();
|
|
SkIntersections oppMid;
|
|
(*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayMid, &oppMid);
|
|
int oppOutside = oppMid.mostOutside(rh->fStart->t(), rh->fEnd->t(), dStartPt);
|
|
if (oppOutside < 0) {
|
|
return false;
|
|
}
|
|
SkDVector iSide = iMid.pt(iOutside) - dStartPt;
|
|
SkDVector oppSide = oppMid.pt(oppOutside) - dStartPt;
|
|
double dir = iSide.crossCheck(oppSide);
|
|
if (!dir) {
|
|
return false;
|
|
}
|
|
*inside = dir < 0;
|
|
return true;
|
|
}
|
|
|
|
bool SkOpAngle::oppositePlanes(const SkOpAngle* rh) const {
|
|
int startSpan = SkTAbs(rh->fSectorStart - fSectorStart);
|
|
return startSpan >= 8;
|
|
}
|
|
|
|
int SkOpAngle::orderable(SkOpAngle* rh) {
|
|
int result;
|
|
if (!fPart.isCurve()) {
|
|
if (!rh->fPart.isCurve()) {
|
|
double leftX = fTangentHalf.dx();
|
|
double leftY = fTangentHalf.dy();
|
|
double rightX = rh->fTangentHalf.dx();
|
|
double rightY = rh->fTangentHalf.dy();
|
|
double x_ry = leftX * rightY;
|
|
double rx_y = rightX * leftY;
|
|
if (x_ry == rx_y) {
|
|
if (leftX * rightX < 0 || leftY * rightY < 0) {
|
|
return 1; // exactly 180 degrees apart
|
|
}
|
|
goto unorderable;
|
|
}
|
|
SkASSERT(x_ry != rx_y); // indicates an undetected coincidence -- worth finding earlier
|
|
return x_ry < rx_y ? 1 : 0;
|
|
}
|
|
if ((result = this->lineOnOneSide(rh, false)) >= 0) {
|
|
return result;
|
|
}
|
|
if (fUnorderable || approximately_zero(rh->fSide)) {
|
|
goto unorderable;
|
|
}
|
|
} else if (!rh->fPart.isCurve()) {
|
|
if ((result = rh->lineOnOneSide(this, false)) >= 0) {
|
|
return result ? 0 : 1;
|
|
}
|
|
if (rh->fUnorderable || approximately_zero(fSide)) {
|
|
goto unorderable;
|
|
}
|
|
} else if ((result = this->convexHullOverlaps(rh)) >= 0) {
|
|
return result;
|
|
}
|
|
return this->endsIntersect(rh) ? 1 : 0;
|
|
unorderable:
|
|
fUnorderable = true;
|
|
rh->fUnorderable = true;
|
|
return -1;
|
|
}
|
|
|
|
// OPTIMIZE: if this shows up in a profile, add a previous pointer
|
|
// as is, this should be rarely called
|
|
SkOpAngle* SkOpAngle::previous() const {
|
|
SkOpAngle* last = fNext;
|
|
do {
|
|
SkOpAngle* next = last->fNext;
|
|
if (next == this) {
|
|
return last;
|
|
}
|
|
last = next;
|
|
} while (true);
|
|
}
|
|
|
|
SkOpSegment* SkOpAngle::segment() const {
|
|
return fStart->segment();
|
|
}
|
|
|
|
void SkOpAngle::set(SkOpSpanBase* start, SkOpSpanBase* end) {
|
|
fStart = start;
|
|
fComputedEnd = fEnd = end;
|
|
SkASSERT(start != end);
|
|
fNext = nullptr;
|
|
fComputeSector = fComputedSector = fCheckCoincidence = fTangentsAmbiguous = false;
|
|
setSpans();
|
|
setSector();
|
|
SkDEBUGCODE(fID = start ? start->globalState()->nextAngleID() : -1);
|
|
}
|
|
|
|
void SkOpAngle::setSpans() {
|
|
fUnorderable = false;
|
|
fLastMarked = nullptr;
|
|
if (!fStart) {
|
|
fUnorderable = true;
|
|
return;
|
|
}
|
|
const SkOpSegment* segment = fStart->segment();
|
|
const SkPoint* pts = segment->pts();
|
|
SkDEBUGCODE(fPart.fCurve.fVerb = SkPath::kCubic_Verb); // required for SkDCurve debug check
|
|
SkDEBUGCODE(fPart.fCurve[2].fX = fPart.fCurve[2].fY = fPart.fCurve[3].fX = fPart.fCurve[3].fY
|
|
= SK_ScalarNaN); // make the non-line part uninitialized
|
|
SkDEBUGCODE(fPart.fCurve.fVerb = segment->verb()); // set the curve type for real
|
|
segment->subDivide(fStart, fEnd, &fPart.fCurve); // set at least the line part if not more
|
|
fOriginalCurvePart = fPart.fCurve;
|
|
const SkPath::Verb verb = segment->verb();
|
|
fPart.setCurveHullSweep(verb);
|
|
if (SkPath::kLine_Verb != verb && !fPart.isCurve()) {
|
|
SkDLine lineHalf;
|
|
fPart.fCurve[1] = fPart.fCurve[SkPathOpsVerbToPoints(verb)];
|
|
fOriginalCurvePart[1] = fPart.fCurve[1];
|
|
lineHalf[0].set(fPart.fCurve[0].asSkPoint());
|
|
lineHalf[1].set(fPart.fCurve[1].asSkPoint());
|
|
fTangentHalf.lineEndPoints(lineHalf);
|
|
fSide = 0;
|
|
}
|
|
switch (verb) {
|
|
case SkPath::kLine_Verb: {
|
|
SkASSERT(fStart != fEnd);
|
|
const SkPoint& cP1 = pts[fStart->t() < fEnd->t()];
|
|
SkDLine lineHalf;
|
|
lineHalf[0].set(fStart->pt());
|
|
lineHalf[1].set(cP1);
|
|
fTangentHalf.lineEndPoints(lineHalf);
|
|
fSide = 0;
|
|
} return;
|
|
case SkPath::kQuad_Verb:
|
|
case SkPath::kConic_Verb: {
|
|
SkLineParameters tangentPart;
|
|
(void) tangentPart.quadEndPoints(fPart.fCurve.fQuad);
|
|
fSide = -tangentPart.pointDistance(fPart.fCurve[2]); // not normalized -- compare sign only
|
|
} break;
|
|
case SkPath::kCubic_Verb: {
|
|
SkLineParameters tangentPart;
|
|
(void) tangentPart.cubicPart(fPart.fCurve.fCubic);
|
|
fSide = -tangentPart.pointDistance(fPart.fCurve[3]);
|
|
double testTs[4];
|
|
// OPTIMIZATION: keep inflections precomputed with cubic segment?
|
|
int testCount = SkDCubic::FindInflections(pts, testTs);
|
|
double startT = fStart->t();
|
|
double endT = fEnd->t();
|
|
double limitT = endT;
|
|
int index;
|
|
for (index = 0; index < testCount; ++index) {
|
|
if (!::between(startT, testTs[index], limitT)) {
|
|
testTs[index] = -1;
|
|
}
|
|
}
|
|
testTs[testCount++] = startT;
|
|
testTs[testCount++] = endT;
|
|
SkTQSort<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, ¤t)) {
|
|
return false;
|
|
}
|
|
SkPathOpsDebug::ShowActiveSpans(contourList);
|
|
if (!current) {
|
|
break;
|
|
}
|
|
} while (true);
|
|
} while (true);
|
|
return true;
|
|
}
|
|
|
|
// diagram of why this simplifcation is possible is here:
|
|
// https://skia.org/dev/present/pathops link at bottom of the page
|
|
// https://drive.google.com/file/d/0BwoLUwz9PYkHLWpsaXd0UDdaN00/view?usp=sharing
|
|
static const SkPathOp gOpInverse[kReverseDifference_SkPathOp + 1][2][2] = {
|
|
// inside minuend outside minuend
|
|
// inside subtrahend outside subtrahend inside subtrahend outside subtrahend
|
|
{{ kDifference_SkPathOp, kIntersect_SkPathOp }, { kUnion_SkPathOp, kReverseDifference_SkPathOp }},
|
|
{{ kIntersect_SkPathOp, kDifference_SkPathOp }, { kReverseDifference_SkPathOp, kUnion_SkPathOp }},
|
|
{{ kUnion_SkPathOp, kReverseDifference_SkPathOp }, { kDifference_SkPathOp, kIntersect_SkPathOp }},
|
|
{{ kXOR_SkPathOp, kXOR_SkPathOp }, { kXOR_SkPathOp, kXOR_SkPathOp }},
|
|
{{ kReverseDifference_SkPathOp, kUnion_SkPathOp }, { kIntersect_SkPathOp, kDifference_SkPathOp }},
|
|
};
|
|
|
|
static const bool gOutInverse[kReverseDifference_SkPathOp + 1][2][2] = {
|
|
{{ false, false }, { true, false }}, // diff
|
|
{{ false, false }, { false, true }}, // sect
|
|
{{ false, true }, { true, true }}, // union
|
|
{{ false, true }, { true, false }}, // xor
|
|
{{ false, true }, { false, false }}, // rev diff
|
|
};
|
|
|
|
#if DEBUG_T_SECT_LOOP_COUNT
|
|
|
|
|
|
SkOpGlobalState debugWorstState(nullptr, nullptr SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr));
|
|
|
|
void ReportPathOpsDebugging() {
|
|
debugWorstState.debugLoopReport();
|
|
}
|
|
|
|
extern void (*gVerboseFinalize)();
|
|
|
|
#endif
|
|
|
|
bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result
|
|
SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) {
|
|
#if DEBUG_DUMP_VERIFY
|
|
#ifndef SK_DEBUG
|
|
const char* testName = "release";
|
|
#endif
|
|
if (SkPathOpsDebug::gDumpOp) {
|
|
DumpOp(one, two, op, testName);
|
|
}
|
|
#endif
|
|
op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()];
|
|
bool inverseFill = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()];
|
|
SkPathFillType fillType = inverseFill ? SkPathFillType::kInverseEvenOdd :
|
|
SkPathFillType::kEvenOdd;
|
|
SkRect rect1, rect2;
|
|
if (kIntersect_SkPathOp == op && one.isRect(&rect1) && two.isRect(&rect2)) {
|
|
result->reset();
|
|
result->setFillType(fillType);
|
|
if (rect1.intersect(rect2)) {
|
|
result->addRect(rect1);
|
|
}
|
|
return true;
|
|
}
|
|
if (one.isEmpty() || two.isEmpty()) {
|
|
SkPath work;
|
|
switch (op) {
|
|
case kIntersect_SkPathOp:
|
|
break;
|
|
case kUnion_SkPathOp:
|
|
case kXOR_SkPathOp:
|
|
work = one.isEmpty() ? two : one;
|
|
break;
|
|
case kDifference_SkPathOp:
|
|
if (!one.isEmpty()) {
|
|
work = one;
|
|
}
|
|
break;
|
|
case kReverseDifference_SkPathOp:
|
|
if (!two.isEmpty()) {
|
|
work = two;
|
|
}
|
|
break;
|
|
default:
|
|
SkASSERT(0); // unhandled case
|
|
}
|
|
if (inverseFill != work.isInverseFillType()) {
|
|
work.toggleInverseFillType();
|
|
}
|
|
return Simplify(work, result);
|
|
}
|
|
SkSTArenaAlloc<4096> allocator; // FIXME: add a constant expression here, tune
|
|
SkOpContour contour;
|
|
SkOpContourHead* contourList = static_cast<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, §1->fHeap);
|
|
span2->addBounded(span1, §2->fHeap);
|
|
const int kMaxCoinLoopCount = 8;
|
|
int coinLoopCount = kMaxCoinLoopCount;
|
|
double start1s SK_INIT_TO_AVOID_WARNING;
|
|
double start1e SK_INIT_TO_AVOID_WARNING;
|
|
do {
|
|
// find the largest bounds
|
|
SkTSpan* largest1 = sect1->boundsMax();
|
|
if (!largest1) {
|
|
if (sect1->fHung) {
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
SkTSpan* largest2 = sect2->boundsMax();
|
|
// split it
|
|
if (!largest2 || (largest1 && (largest1->fBoundsMax > largest2->fBoundsMax
|
|
|| (!largest1->fCollapsed && largest2->fCollapsed)))) {
|
|
if (sect2->fHung) {
|
|
return;
|
|
}
|
|
if (largest1->fCollapsed) {
|
|
break;
|
|
}
|
|
sect1->resetRemovedEnds();
|
|
sect2->resetRemovedEnds();
|
|
// trim parts that don't intersect the opposite
|
|
SkTSpan* half1 = sect1->addOne();
|
|
SkDEBUGCODE(half1->debugSetGlobalState(sect1->globalState()));
|
|
if (!half1->split(largest1, §1->fHeap)) {
|
|
break;
|
|
}
|
|
if (!sect1->trim(largest1, sect2)) {
|
|
SkOPOBJASSERT(intersections, 0);
|
|
return;
|
|
}
|
|
if (!sect1->trim(half1, sect2)) {
|
|
SkOPOBJASSERT(intersections, 0);
|
|
return;
|
|
}
|
|
} else {
|
|
if (largest2->fCollapsed) {
|
|
break;
|
|
}
|
|
sect1->resetRemovedEnds();
|
|
sect2->resetRemovedEnds();
|
|
// trim parts that don't intersect the opposite
|
|
SkTSpan* half2 = sect2->addOne();
|
|
SkDEBUGCODE(half2->debugSetGlobalState(sect2->globalState()));
|
|
if (!half2->split(largest2, §2->fHeap)) {
|
|
break;
|
|
}
|
|
if (!sect2->trim(largest2, sect1)) {
|
|
SkOPOBJASSERT(intersections, 0);
|
|
return;
|
|
}
|
|
if (!sect2->trim(half2, sect1)) {
|
|
SkOPOBJASSERT(intersections, 0);
|
|
return;
|
|
}
|
|
}
|
|
sect1->validate();
|
|
sect2->validate();
|
|
#if DEBUG_T_SECT_LOOP_COUNT
|
|
intersections->debugBumpLoopCount(SkIntersections::kIterations_DebugLoop);
|
|
#endif
|
|
// if there are 9 or more continuous spans on both sects, suspect coincidence
|
|
if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT
|
|
&& sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) {
|
|
if (coinLoopCount == kMaxCoinLoopCount) {
|
|
start1s = sect1->fHead->fStartT;
|
|
start1e = sect1->tail()->fEndT;
|
|
}
|
|
if (!sect1->coincidentCheck(sect2)) {
|
|
return;
|
|
}
|
|
sect1->validate();
|
|
sect2->validate();
|
|
#if DEBUG_T_SECT_LOOP_COUNT
|
|
intersections->debugBumpLoopCount(SkIntersections::kCoinCheck_DebugLoop);
|
|
#endif
|
|
if (!--coinLoopCount && sect1->fHead && sect2->fHead) {
|
|
/* All known working cases resolve in two tries. Sadly, cubicConicTests[0]
|
|
gets stuck in a loop. It adds an extension to allow a coincident end
|
|
perpendicular to track its intersection in the opposite curve. However,
|
|
the bounding box of the extension does not intersect the original curve,
|
|
so the extension is discarded, only to be added again the next time around. */
|
|
sect1->coincidentForce(sect2, start1s, start1e);
|
|
sect1->validate();
|
|
sect2->validate();
|
|
}
|
|
}
|
|
if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT
|
|
&& sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) {
|
|
if (!sect1->fHead) {
|
|
return;
|
|
}
|
|
sect1->computePerpendiculars(sect2, sect1->fHead, sect1->tail());
|
|
if (!sect2->fHead) {
|
|
return;
|
|
}
|
|
sect2->computePerpendiculars(sect1, sect2->fHead, sect2->tail());
|
|
if (!sect1->removeByPerpendicular(sect2)) {
|
|
return;
|
|
}
|
|
sect1->validate();
|
|
sect2->validate();
|
|
#if DEBUG_T_SECT_LOOP_COUNT
|
|
intersections->debugBumpLoopCount(SkIntersections::kComputePerp_DebugLoop);
|
|
#endif
|
|
if (sect1->collapsed() > sect1->fCurve.maxIntersections()) {
|
|
break;
|
|
}
|
|
}
|
|
#if DEBUG_T_SECT_DUMP
|
|
sect1->dumpBoth(sect2);
|
|
#endif
|
|
if (!sect1->fHead || !sect2->fHead) {
|
|
break;
|
|
}
|
|
} while (true);
|
|
SkTSpan* coincident = sect1->fCoincident;
|
|
if (coincident) {
|
|
// if there is more than one coincident span, check loosely to see if they should be joined
|
|
if (coincident->fNext) {
|
|
sect1->mergeCoincidence(sect2);
|
|
coincident = sect1->fCoincident;
|
|
}
|
|
SkASSERT(sect2->fCoincident); // courtesy check : coincidence only looks at sect 1
|
|
do {
|
|
if (!coincident) {
|
|
return;
|
|
}
|
|
if (!coincident->fCoinStart.isMatch()) {
|
|
continue;
|
|
}
|
|
if (!coincident->fCoinEnd.isMatch()) {
|
|
continue;
|
|
}
|
|
double perpT = coincident->fCoinStart.perpT();
|
|
if (perpT < 0) {
|
|
return;
|
|
}
|
|
int index = intersections->insertCoincident(coincident->fStartT,
|
|
perpT, coincident->pointFirst());
|
|
if ((intersections->insertCoincident(coincident->fEndT,
|
|
coincident->fCoinEnd.perpT(),
|
|
coincident->pointLast()) < 0) && index >= 0) {
|
|
intersections->clearCoincidence(index);
|
|
}
|
|
} while ((coincident = coincident->fNext));
|
|
}
|
|
int zeroOneSet = EndsEqual(sect1, sect2, intersections);
|
|
// if (!sect1->fHead || !sect2->fHead) {
|
|
// if the final iteration contains an end (0 or 1),
|
|
if (sect1->fRemovedStartT && !(zeroOneSet & kZeroS1Set)) {
|
|
SkTCoincident perp; // intersect perpendicular with opposite curve
|
|
perp.setPerp(sect1->fCurve, 0, sect1->fCurve[0], sect2->fCurve);
|
|
if (perp.isMatch()) {
|
|
intersections->insert(0, perp.perpT(), perp.perpPt());
|
|
}
|
|
}
|
|
if (sect1->fRemovedEndT && !(zeroOneSet & kOneS1Set)) {
|
|
SkTCoincident perp;
|
|
perp.setPerp(sect1->fCurve, 1, sect1->pointLast(), sect2->fCurve);
|
|
if (perp.isMatch()) {
|
|
intersections->insert(1, perp.perpT(), perp.perpPt());
|
|
}
|
|
}
|
|
if (sect2->fRemovedStartT && !(zeroOneSet & kZeroS2Set)) {
|
|
SkTCoincident perp;
|
|
perp.setPerp(sect2->fCurve, 0, sect2->fCurve[0], sect1->fCurve);
|
|
if (perp.isMatch()) {
|
|
intersections->insert(perp.perpT(), 0, perp.perpPt());
|
|
}
|
|
}
|
|
if (sect2->fRemovedEndT && !(zeroOneSet & kOneS2Set)) {
|
|
SkTCoincident perp;
|
|
perp.setPerp(sect2->fCurve, 1, sect2->pointLast(), sect1->fCurve);
|
|
if (perp.isMatch()) {
|
|
intersections->insert(perp.perpT(), 1, perp.perpPt());
|
|
}
|
|
}
|
|
// }
|
|
if (!sect1->fHead || !sect2->fHead) {
|
|
return;
|
|
}
|
|
sect1->recoverCollapsed();
|
|
sect2->recoverCollapsed();
|
|
SkTSpan* result1 = sect1->fHead;
|
|
// check heads and tails for zero and ones and insert them if we haven't already done so
|
|
const SkTSpan* head1 = result1;
|
|
if (!(zeroOneSet & kZeroS1Set) && approximately_less_than_zero(head1->fStartT)) {
|
|
const SkDPoint& start1 = sect1->fCurve[0];
|
|
if (head1->isBounded()) {
|
|
double t = head1->closestBoundedT(start1);
|
|
if (sect2->fCurve.ptAtT(t).approximatelyEqual(start1)) {
|
|
intersections->insert(0, t, start1);
|
|
}
|
|
}
|
|
}
|
|
const SkTSpan* head2 = sect2->fHead;
|
|
if (!(zeroOneSet & kZeroS2Set) && approximately_less_than_zero(head2->fStartT)) {
|
|
const SkDPoint& start2 = sect2->fCurve[0];
|
|
if (head2->isBounded()) {
|
|
double t = head2->closestBoundedT(start2);
|
|
if (sect1->fCurve.ptAtT(t).approximatelyEqual(start2)) {
|
|
intersections->insert(t, 0, start2);
|
|
}
|
|
}
|
|
}
|
|
if (!(zeroOneSet & kOneS1Set)) {
|
|
const SkTSpan* tail1 = sect1->tail();
|
|
if (!tail1) {
|
|
return;
|
|
}
|
|
if (approximately_greater_than_one(tail1->fEndT)) {
|
|
const SkDPoint& end1 = sect1->pointLast();
|
|
if (tail1->isBounded()) {
|
|
double t = tail1->closestBoundedT(end1);
|
|
if (sect2->fCurve.ptAtT(t).approximatelyEqual(end1)) {
|
|
intersections->insert(1, t, end1);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (!(zeroOneSet & kOneS2Set)) {
|
|
const SkTSpan* tail2 = sect2->tail();
|
|
if (!tail2) {
|
|
return;
|
|
}
|
|
if (approximately_greater_than_one(tail2->fEndT)) {
|
|
const SkDPoint& end2 = sect2->pointLast();
|
|
if (tail2->isBounded()) {
|
|
double t = tail2->closestBoundedT(end2);
|
|
if (sect1->fCurve.ptAtT(t).approximatelyEqual(end2)) {
|
|
intersections->insert(t, 1, end2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
SkClosestSect closest;
|
|
do {
|
|
while (result1 && result1->fCoinStart.isMatch() && result1->fCoinEnd.isMatch()) {
|
|
result1 = result1->fNext;
|
|
}
|
|
if (!result1) {
|
|
break;
|
|
}
|
|
SkTSpan* result2 = sect2->fHead;
|
|
while (result2) {
|
|
closest.find(result1, result2 SkDEBUGPARAMS(intersections));
|
|
result2 = result2->fNext;
|
|
}
|
|
} while ((result1 = result1->fNext));
|
|
closest.finish(intersections);
|
|
// if there is more than one intersection and it isn't already coincident, check
|
|
int last = intersections->used() - 1;
|
|
for (int index = 0; index < last; ) {
|
|
if (intersections->isCoincident(index) && intersections->isCoincident(index + 1)) {
|
|
++index;
|
|
continue;
|
|
}
|
|
double midT = ((*intersections)[0][index] + (*intersections)[0][index + 1]) / 2;
|
|
SkDPoint midPt = sect1->fCurve.ptAtT(midT);
|
|
// intersect perpendicular with opposite curve
|
|
SkTCoincident perp;
|
|
perp.setPerp(sect1->fCurve, midT, midPt, sect2->fCurve);
|
|
if (!perp.isMatch()) {
|
|
++index;
|
|
continue;
|
|
}
|
|
if (intersections->isCoincident(index)) {
|
|
intersections->removeOne(index);
|
|
--last;
|
|
} else if (intersections->isCoincident(index + 1)) {
|
|
intersections->removeOne(index + 1);
|
|
--last;
|
|
} else {
|
|
intersections->setCoincident(index++);
|
|
}
|
|
intersections->setCoincident(index);
|
|
}
|
|
SkOPOBJASSERT(intersections, intersections->used() <= sect1->fCurve.maxIntersections());
|
|
}
|
|
|
|
int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) {
|
|
SkTQuad quad1(q1);
|
|
SkTQuad quad2(q2);
|
|
SkTSect sect1(quad1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1));
|
|
SkTSect sect2(quad2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2));
|
|
SkTSect::BinarySearch(§1, §2, this);
|
|
return used();
|
|
}
|
|
|
|
int SkIntersections::intersect(const SkDConic& c, const SkDQuad& q) {
|
|
SkTConic conic(c);
|
|
SkTQuad quad(q);
|
|
SkTSect sect1(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1));
|
|
SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2));
|
|
SkTSect::BinarySearch(§1, §2, this);
|
|
return used();
|
|
}
|
|
|
|
int SkIntersections::intersect(const SkDConic& c1, const SkDConic& c2) {
|
|
SkTConic conic1(c1);
|
|
SkTConic conic2(c2);
|
|
SkTSect sect1(conic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1));
|
|
SkTSect sect2(conic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2));
|
|
SkTSect::BinarySearch(§1, §2, this);
|
|
return used();
|
|
}
|
|
|
|
int SkIntersections::intersect(const SkDCubic& c, const SkDQuad& q) {
|
|
SkTCubic cubic(c);
|
|
SkTQuad quad(q);
|
|
SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1));
|
|
SkTSect sect2(quad SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2));
|
|
SkTSect::BinarySearch(§1, §2, this);
|
|
return used();
|
|
}
|
|
|
|
int SkIntersections::intersect(const SkDCubic& cu, const SkDConic& co) {
|
|
SkTCubic cubic(cu);
|
|
SkTConic conic(co);
|
|
SkTSect sect1(cubic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1));
|
|
SkTSect sect2(conic SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2));
|
|
SkTSect::BinarySearch(§1, §2, this);
|
|
return used();
|
|
|
|
}
|
|
|
|
int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) {
|
|
SkTCubic cubic1(c1);
|
|
SkTCubic cubic2(c2);
|
|
SkTSect sect1(cubic1 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1));
|
|
SkTSect sect2(cubic2 SkDEBUGPARAMS(globalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2));
|
|
SkTSect::BinarySearch(§1, §2, this);
|
|
return used();
|
|
}
|
|
|
|
static bool arguments_denormalized(float a, float b, int epsilon) {
|
|
float denormalizedCheck = FLT_EPSILON * epsilon / 2;
|
|
return fabsf(a) <= denormalizedCheck && fabsf(b) <= denormalizedCheck;
|
|
}
|
|
|
|
// from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
|
|
// FIXME: move to SkFloatBits.h
|
|
static bool equal_ulps(float a, float b, int epsilon, int depsilon) {
|
|
if (arguments_denormalized(a, b, depsilon)) {
|
|
return true;
|
|
}
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits < bBits + epsilon && bBits < aBits + epsilon;
|
|
}
|
|
|
|
static bool equal_ulps_no_normal_check(float a, float b, int epsilon, int depsilon) {
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits < bBits + epsilon && bBits < aBits + epsilon;
|
|
}
|
|
|
|
static bool equal_ulps_pin(float a, float b, int epsilon, int depsilon) {
|
|
if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) {
|
|
return false;
|
|
}
|
|
if (arguments_denormalized(a, b, depsilon)) {
|
|
return true;
|
|
}
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits < bBits + epsilon && bBits < aBits + epsilon;
|
|
}
|
|
|
|
static bool d_equal_ulps(float a, float b, int epsilon) {
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits < bBits + epsilon && bBits < aBits + epsilon;
|
|
}
|
|
|
|
static bool not_equal_ulps(float a, float b, int epsilon) {
|
|
if (arguments_denormalized(a, b, epsilon)) {
|
|
return false;
|
|
}
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits >= bBits + epsilon || bBits >= aBits + epsilon;
|
|
}
|
|
|
|
static bool not_equal_ulps_pin(float a, float b, int epsilon) {
|
|
if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) {
|
|
return false;
|
|
}
|
|
if (arguments_denormalized(a, b, epsilon)) {
|
|
return false;
|
|
}
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits >= bBits + epsilon || bBits >= aBits + epsilon;
|
|
}
|
|
|
|
static bool d_not_equal_ulps(float a, float b, int epsilon) {
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits >= bBits + epsilon || bBits >= aBits + epsilon;
|
|
}
|
|
|
|
static bool less_ulps(float a, float b, int epsilon) {
|
|
if (arguments_denormalized(a, b, epsilon)) {
|
|
return a <= b - FLT_EPSILON * epsilon;
|
|
}
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits <= bBits - epsilon;
|
|
}
|
|
|
|
static bool less_or_equal_ulps(float a, float b, int epsilon) {
|
|
if (arguments_denormalized(a, b, epsilon)) {
|
|
return a < b + FLT_EPSILON * epsilon;
|
|
}
|
|
int aBits = SkFloatAs2sCompliment(a);
|
|
int bBits = SkFloatAs2sCompliment(b);
|
|
// Find the difference in ULPs.
|
|
return aBits < bBits + epsilon;
|
|
}
|
|
|
|
// equality using the same error term as between
|
|
bool AlmostBequalUlps(float a, float b) {
|
|
const int UlpsEpsilon = 2;
|
|
return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostPequalUlps(float a, float b) {
|
|
const int UlpsEpsilon = 8;
|
|
return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostDequalUlps(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return d_equal_ulps(a, b, UlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostDequalUlps(double a, double b) {
|
|
if (fabs(a) < SK_ScalarMax && fabs(b) < SK_ScalarMax) {
|
|
return AlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b));
|
|
}
|
|
// We allow divide-by-zero here. It only happens if one of a,b is zero, and the other is NaN.
|
|
// (Otherwise, we'd hit the condition above). Thus, if std::max returns 0, we compute NaN / 0,
|
|
// which will produce NaN. The comparison will return false, which is the correct answer.
|
|
return sk_ieee_double_divide(fabs(a - b), std::max(fabs(a), fabs(b))) < FLT_EPSILON * 16;
|
|
}
|
|
|
|
bool AlmostEqualUlps(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostEqualUlpsNoNormalCheck(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return equal_ulps_no_normal_check(a, b, UlpsEpsilon, UlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostEqualUlps_Pin(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return equal_ulps_pin(a, b, UlpsEpsilon, UlpsEpsilon);
|
|
}
|
|
|
|
bool NotAlmostEqualUlps(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return not_equal_ulps(a, b, UlpsEpsilon);
|
|
}
|
|
|
|
bool NotAlmostEqualUlps_Pin(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return not_equal_ulps_pin(a, b, UlpsEpsilon);
|
|
}
|
|
|
|
bool NotAlmostDequalUlps(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return d_not_equal_ulps(a, b, UlpsEpsilon);
|
|
}
|
|
|
|
bool RoughlyEqualUlps(float a, float b) {
|
|
const int UlpsEpsilon = 256;
|
|
const int DUlpsEpsilon = 1024;
|
|
return equal_ulps(a, b, UlpsEpsilon, DUlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostBetweenUlps(float a, float b, float c) {
|
|
const int UlpsEpsilon = 2;
|
|
return a <= c ? less_or_equal_ulps(a, b, UlpsEpsilon) && less_or_equal_ulps(b, c, UlpsEpsilon)
|
|
: less_or_equal_ulps(b, a, UlpsEpsilon) && less_or_equal_ulps(c, b, UlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostLessUlps(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return less_ulps(a, b, UlpsEpsilon);
|
|
}
|
|
|
|
bool AlmostLessOrEqualUlps(float a, float b) {
|
|
const int UlpsEpsilon = 16;
|
|
return less_or_equal_ulps(a, b, UlpsEpsilon);
|
|
}
|
|
|
|
int UlpsDistance(float a, float b) {
|
|
SkFloatIntUnion floatIntA, floatIntB;
|
|
floatIntA.fFloat = a;
|
|
floatIntB.fFloat = b;
|
|
// Different signs means they do not match.
|
|
if ((floatIntA.fSignBitInt < 0) != (floatIntB.fSignBitInt < 0)) {
|
|
// Check for equality to make sure +0 == -0
|
|
return a == b ? 0 : SK_MaxS32;
|
|
}
|
|
// Find the difference in ULPs.
|
|
return SkTAbs(floatIntA.fSignBitInt - floatIntB.fSignBitInt);
|
|
}
|
|
|
|
SkOpGlobalState::SkOpGlobalState(SkOpContourHead* head,
|
|
SkArenaAlloc* allocator
|
|
SkDEBUGPARAMS(bool debugSkipAssert)
|
|
SkDEBUGPARAMS(const char* testName))
|
|
: fAllocator(allocator)
|
|
, fCoincidence(nullptr)
|
|
, fContourHead(head)
|
|
, fNested(0)
|
|
, fWindingFailed(false)
|
|
, fPhase(SkOpPhase::kIntersecting)
|
|
SkDEBUGPARAMS(fDebugTestName(testName))
|
|
SkDEBUGPARAMS(fAngleID(0))
|
|
SkDEBUGPARAMS(fCoinID(0))
|
|
SkDEBUGPARAMS(fContourID(0))
|
|
SkDEBUGPARAMS(fPtTID(0))
|
|
SkDEBUGPARAMS(fSegmentID(0))
|
|
SkDEBUGPARAMS(fSpanID(0))
|
|
SkDEBUGPARAMS(fDebugSkipAssert(debugSkipAssert)) {
|
|
#if DEBUG_T_SECT_LOOP_COUNT
|
|
debugResetLoopCounts();
|
|
#endif
|
|
#if DEBUG_COIN
|
|
fPreviousFuncName = nullptr;
|
|
#endif
|
|
}
|
|
|
|
// given a prospective edge, compute its initial winding by projecting a ray
|
|
// if the ray hits another edge
|
|
// if the edge doesn't have a winding yet, hop up to that edge and start over
|
|
// concern : check for hops forming a loop
|
|
// if the edge is unsortable, or
|
|
// the intersection is nearly at the ends, or
|
|
// the tangent at the intersection is nearly coincident to the ray,
|
|
// choose a different ray and try again
|
|
// concern : if it is unable to succeed after N tries, try another edge? direction?
|
|
// if no edge is hit, compute the winding directly
|
|
|
|
// given the top span, project the most perpendicular ray and look for intersections
|
|
// let's try up and then down. What the hey
|
|
|
|
// bestXY is initialized by caller with basePt
|
|
|
|
|
|
|
|
using namespace skia_private;
|
|
|
|
enum class SkOpRayDir {
|
|
kLeft,
|
|
kTop,
|
|
kRight,
|
|
kBottom,
|
|
};
|
|
|
|
#if DEBUG_WINDING
|
|
const char* gDebugRayDirName[] = {
|
|
"kLeft",
|
|
"kTop",
|
|
"kRight",
|
|
"kBottom"
|
|
};
|
|
#endif
|
|
|
|
static int xy_index(SkOpRayDir dir) {
|
|
return static_cast<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
|