示例#1
0
static void addPolyCurveToPath(const TTPOLYCURVE* polyCurve, SkPath* path)
{
    switch (polyCurve->wType) {
    case TT_PRIM_LINE:
        for (WORD i = 0; i < polyCurve->cpfx; i++) {
          path->lineTo(FIXEDToSkScalar(polyCurve->apfx[i].x), -FIXEDToSkScalar(polyCurve->apfx[i].y));
        }
        break;

    case TT_PRIM_QSPLINE:
        // FIXME: doesn't this duplicate points if we do the loop > once?
        for (WORD i = 0; i < polyCurve->cpfx - 1; i++) {
            SkScalar bx = FIXEDToSkScalar(polyCurve->apfx[i].x);
            SkScalar by = FIXEDToSkScalar(polyCurve->apfx[i].y);

            SkScalar cx = FIXEDToSkScalar(polyCurve->apfx[i + 1].x);
            SkScalar cy = FIXEDToSkScalar(polyCurve->apfx[i + 1].y);
            if (i < polyCurve->cpfx - 2) {
                // We're not the last point, compute C.
                cx = SkScalarAve(bx, cx);
                cy = SkScalarAve(by, cy);
            }

            // Need to flip the y coordinates since the font's coordinate system is
            // flipped from ours vertically.
            path->quadTo(bx, -by, cx, -cy);
        }
        break;

    case TT_PRIM_CSPLINE:
        // FIXME
        break;
    }
}
示例#2
0
/*  TODO

Need differentially more subdivisions when the follow-path is curvy. Not sure how to
determine that, but we need it. I guess a cheap answer is let the caller tell us,
but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
*/
static void morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
                      SkScalar dist) {
    SkPath::Iter    iter(src, false);
    SkPoint         srcP[4], dstP[3];
    SkPath::Verb    verb;

    while ((verb = iter.next(srcP)) != SkPath::kDone_Verb) {
        switch (verb) {
            case SkPath::kMove_Verb:
                morphpoints(dstP, srcP, 1, meas, dist);
                dst->moveTo(dstP[0]);
                break;
            case SkPath::kLine_Verb:
                srcP[2] = srcP[1];
                srcP[1].set(SkScalarAve(srcP[0].fX, srcP[2].fX),
                            SkScalarAve(srcP[0].fY, srcP[2].fY));
                // fall through to quad
            case SkPath::kQuad_Verb:
                morphpoints(dstP, &srcP[1], 2, meas, dist);
                dst->quadTo(dstP[0], dstP[1]);
                break;
            case SkPath::kCubic_Verb:
                morphpoints(dstP, &srcP[1], 3, meas, dist);
                dst->cubicTo(dstP[0], dstP[1], dstP[2]);
                break;
            case SkPath::kClose_Verb:
                dst->close();
                break;
            default:
                SkDEBUGFAIL("unknown verb");
                break;
        }
    }
}
示例#3
0
static SkShader* MakeRadial(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
    SkPoint center;
    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
               SkScalarAve(pts[0].fY, pts[1].fY));
    return SkGradientShader::CreateRadial(center, center.fX, data.fColors,
                                          data.fPos, data.fCount, tm);
}
示例#4
0
static sk_sp<SkShader> make_radial_gradient(const SkPoint pts[2], const SkMatrix& localMatrix) {
    SkPoint center;
    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
               SkScalarAve(pts[0].fY, pts[1].fY));
    float radius = (center - pts[0]).length();
    return SkGradientShader::MakeRadial(center, radius, gColors, nullptr, SK_ARRAY_COUNT(gColors),
                                        SkTileMode::kClamp, 0, &localMatrix);
}
示例#5
0
static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data,
                                 SkShader::TileMode, const SkMatrix& localMatrix) {
    SkPoint center;
    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
               SkScalarAve(pts[0].fY, pts[1].fY));
    return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount,
                                       0, &localMatrix);
}
示例#6
0
static SkShader* MakeSweep(const SkPoint pts[2], const GradData& data,
                           SkShader::TileMode tm, SkUnitMapper* mapper) {
    SkPoint center;
    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
               SkScalarAve(pts[0].fY, pts[1].fY));
    return SkGradientShader::CreateSweep(center.fX, center.fY, data.fColors,
                                         data.fPos, data.fCount, mapper);
}
示例#7
0
static SkShader* make_radial_gradient(const SkPoint pts[2], const SkMatrix& localMatrix) {
    SkPoint center;
    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
               SkScalarAve(pts[0].fY, pts[1].fY));
    float radius = (center - pts[0]).length();
    return SkGradientShader::CreateRadial(center, radius, gColors, NULL, SK_ARRAY_COUNT(gColors),
                                          SkShader::kClamp_TileMode, 0, &localMatrix);
}
示例#8
0
static sk_sp<SkShader> MakeRadial4f(const SkPoint pts[2], const GradData& data,
                                    SkShader::TileMode tm, const SkMatrix& localMatrix) {
    SkPoint center;
    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
               SkScalarAve(pts[0].fY, pts[1].fY));
    auto srgb = SkColorSpace::MakeSRGBLinear();
    return SkGradientShader::MakeRadial(center, center.fX, data.fColors4f, srgb, data.fPos,
                                        data.fCount, tm, 0, &localMatrix);
}
示例#9
0
static SkShader* Make2ConicalConcentric(const SkPoint pts[2], const GradData& data,
                                       SkShader::TileMode tm) {
    SkPoint center;
    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
               SkScalarAve(pts[0].fY, pts[1].fY));
    return SkGradientShader::CreateTwoPointConical(
                            center, (pts[1].fX - pts[0].fX) / 7,
                            center, (pts[1].fX - pts[0].fX) / 2,
                            data.fColors, data.fPos, data.fCount, tm);
}
static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, SkShader::TileMode tm) {
    SkPoint center0, center1;
    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
    return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
                                                  center0, (pts[1].fX - pts[0].fX) / 2,
                                                  data.fColors, data.fPos, data.fCount, tm);
}
示例#11
0
SkPoint SkBoundaryPatch::eval(SkScalar unitU, SkScalar unitV) {
    SkBoundary* b = fBoundary;
    SkPoint u = SkPointInterp(b->eval(SkBoundary::kLeft, SK_Scalar1 - unitV),
                              b->eval(SkBoundary::kRight, unitV),
                              unitU);
    SkPoint v = SkPointInterp(b->eval(SkBoundary::kTop, unitU),
                              b->eval(SkBoundary::kBottom, SK_Scalar1 - unitU),
                              unitV);
    return SkMakePoint(SkScalarAve(u.fX, v.fX),
                       SkScalarAve(u.fY, v.fY));
}
示例#12
0
static sk_sp<SkShader> Make2ConicalTouchY(const SkPoint pts[2], const GradData& data,
                                          SkShader::TileMode tm, const SkMatrix& localMatrix) {
    SkPoint center0, center1;
    SkScalar radius0 = (pts[1].fX - pts[0].fX) / 7;
    SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
    center1.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center0.set(center1.fX, center1.fY + radius1 - radius0);
    return SkGradientShader::MakeTwoPointConical(center0, radius0, center1, radius1, data.fColors,
                                                 data.fPos, data.fCount, tm, 0, &localMatrix);
}
示例#13
0
static SkShader* Make2Radial(const SkPoint pts[2], const GradData& data,
                             SkShader::TileMode tm, SkUnitMapper* mapper) {
    SkPoint center0, center1;
    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
    return SkGradientShader::CreateTwoPointRadial(
                                                  center1, (pts[1].fX - pts[0].fX) / 7,
                                                  center0, (pts[1].fX - pts[0].fX) / 2,
                                                  data.fColors, data.fPos, data.fCount, tm, mapper);
}
示例#14
0
static SkShader* Make2ConicalInsideCenter(const SkPoint pts[2], const GradData& data,
        SkShader::TileMode tm, const SkMatrix& localMatrix) {
    SkPoint center0, center1;
    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
    return SkGradientShader::CreateTwoPointConical(center0, (pts[1].fX - pts[0].fX) / 7,
            center0, (pts[1].fX - pts[0].fX) / 2,
            data.fColors, data.fPos, data.fCount, tm,
            0, &localMatrix);
}
示例#15
0
static sk_sp<SkShader> Make2Radial4f(const SkPoint pts[2], const GradData& data,
                                     SkShader::TileMode tm, const SkMatrix& localMatrix) {
    SkPoint center0, center1;
    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3) / 5),
                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1) / 4));
    auto srgb = SkColorSpace::MakeSRGBLinear();
    return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
                                                 center0, (pts[1].fX - pts[0].fX) / 2,
                                                 data.fColors4f, srgb, data.fPos, data.fCount, tm,
                                                 0, &localMatrix);
}
示例#16
0
static SkShader* Make2ConicalTouchX(const SkPoint pts[2], const GradData& data,
                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
    SkPoint center0, center1;
    SkScalar radius0 = SkScalarDiv(pts[1].fX - pts[0].fX, 7);
    SkScalar radius1 = SkScalarDiv(pts[1].fX - pts[0].fX, 3);
    center1.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center0.set(center1.fX - radius1 + radius0, center1.fY);
    return SkGradientShader::CreateTwoPointConical(center0, radius0,
                                                   center1, radius1,
                                                   data.fColors, data.fPos,
                                                   data.fCount, tm, 0, &localMatrix);
}
示例#17
0
static SkShader* Make2ConicalZeroRadEdgeY(const SkPoint pts[2], const GradData& data,
        SkShader::TileMode tm, const SkMatrix& localMatrix) {
    SkPoint center0, center1;
    SkScalar radius0 = 0.f;
    SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
    center1.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center0.set(center1.fX, center1.fY + radius1);
    return SkGradientShader::CreateTwoPointConical(center0, radius0,
            center1, radius1,
            data.fColors, data.fPos,
            data.fCount, tm, 0, &localMatrix);
}
示例#18
0
/*  Given 4 cubic points (either Xs or Ys), and a target X or Y, compute the
    t value such that cubic(t) = target
 */
static bool chopMonoCubicAt(SkScalar c0, SkScalar c1, SkScalar c2, SkScalar c3,
                           SkScalar target, SkScalar* t) {
 //   SkASSERT(c0 <= c1 && c1 <= c2 && c2 <= c3);
    SkASSERT(c0 < target && target < c3);

    SkScalar D = c0 - target;
    SkScalar A = c3 + 3*(c1 - c2) - c0;
    SkScalar B = 3*(c2 - c1 - c1 + c0);
    SkScalar C = 3*(c1 - c0);

    const SkScalar TOLERANCE = SK_Scalar1 / 4096;
    SkScalar minT = 0;
    SkScalar maxT = SK_Scalar1;
    SkScalar mid;
    int i;
    for (i = 0; i < 16; i++) {
        mid = SkScalarAve(minT, maxT);
        SkScalar delta = eval_cubic_coeff(A, B, C, D, mid);
        if (delta < 0) {
            minT = mid;
            delta = -delta;
        } else {
            maxT = mid;
        }
        if (delta < TOLERANCE) {
            break;
        }
    }
    *t = mid;
//    SkDebugf("-- evalCubicAt %d delta %g\n", i, eval_cubic_coeff(A, B, C, D, *t));
    return true;
}
示例#19
0
/*  TODO

    Need differentially more subdivisions when the follow-path is curvy. Not sure how to
    determine that, but we need it. I guess a cheap answer is let the caller tell us,
    but that seems like a cop-out. Another answer is to get Rob Johnson to figure it out.
*/
void TextArt::EnvelopeWarp::morphpath(SkPath* dst, const SkPath& src, SkPathMeasure& meas,
                      const SkMatrix& matrix)
{
    SkPath::Iter    iter(src, false);
    SkPoint         srcP[4], dstP[3];
    SkPath::Verb    verb;

    while ((verb = iter.next(srcP)) != SkPath::kDone_Verb)
	{
        switch (verb)
		{
            case SkPath::kMove_Verb:
                morphpoints(dstP, srcP, 1, meas, matrix);
                dst->moveTo(dstP[0]);
                break;
            case SkPath::kLine_Verb:
/*
                morphpoints(dstP, &srcP[1], 1, meas, matrix);
                dst->lineTo(dstP[0]);
                break;
*/

                // turn lines into quads to look bendy
                srcP[0].fX = SkScalarAve(srcP[0].fX, srcP[1].fX);
                srcP[0].fY = SkScalarAve(srcP[0].fY, srcP[1].fY);
                morphpoints(dstP, srcP, 2, meas, matrix);
                dst->quadTo(dstP[0], dstP[1]);
                break;
            case SkPath::kQuad_Verb:
                morphpoints(dstP, &srcP[1], 2, meas, matrix);
                dst->quadTo(dstP[0], dstP[1]);
                break;
            case SkPath::kCubic_Verb:
                morphpoints(dstP, &srcP[1], 3, meas, matrix);
                dst->cubicTo(dstP[0], dstP[1], dstP[2]);
                break;
            case SkPath::kClose_Verb:
                dst->close();
                break;
            default:
                SkDEBUGFAIL("unknown verb");
                break;
        }
    }
}
// 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 {
        return src[0].fX + SkScalarMulDiv(Y - src[0].fY, src[1].fX - src[0].fX,
                                          dy);
    }
}
// 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 {
        return src[0].fY + SkScalarMulDiv(X - src[0].fX, src[1].fY - src[0].fY,
                                          dx);
    }
}
示例#22
0
/*
 * Spits out a dummy gradient to test blur with shader on paint
 */
static sk_sp<SkShader> MakeRadial() {
    SkPoint pts[2] = {
        { 0, 0 },
        { SkIntToScalar(100), SkIntToScalar(100) }
    };
    SkShader::TileMode tm = SkShader::kClamp_TileMode;
    const SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, };
    const SkScalar pos[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
    SkMatrix scale;
    scale.setScale(0.5f, 0.5f);
    scale.postTranslate(5.f, 5.f);
    SkPoint center0, center1;
    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
                SkScalarAve(pts[0].fY, pts[1].fY));
    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
    return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
                                                 center0, (pts[1].fX - pts[0].fX) / 2,
                                                 colors, pos, SK_ARRAY_COUNT(colors), tm,
                                                 0, &scale);
}
示例#23
0
// 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;
    }
}
示例#24
0
void SkChopQuadAtHalf(const SkPoint src[3], SkPoint dst[5]) {
    SkScalar x01 = SkScalarAve(src[0].fX, src[1].fX);
    SkScalar y01 = SkScalarAve(src[0].fY, src[1].fY);
    SkScalar x12 = SkScalarAve(src[1].fX, src[2].fX);
    SkScalar y12 = SkScalarAve(src[1].fY, src[2].fY);

    dst[0] = src[0];
    dst[1].set(x01, y01);
    dst[2].set(SkScalarAve(x01, x12), SkScalarAve(y01, y12));
    dst[3].set(x12, y12);
    dst[4] = src[2];
}
示例#25
0
// 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 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 {
#ifdef SK_SCALAR_IS_FLOAT
        // 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);
        return (float)result;
#else
        return src[0].fX + SkScalarMulDiv(Y - src[0].fY, src[1].fX - src[0].fX,
                                          dy);
#endif
    }
}
示例#27
0
static int winding_mono_quad(const SkPoint pts[], SkScalar x, SkScalar y) {
    SkScalar y0 = pts[0].fY;
    SkScalar y2 = pts[2].fY;
    
    int dir = 1;
    if (y0 > y2) {
        SkTSwap(y0, y2);
        dir = -1;
    }
    if (y < y0 || y >= y2) {
        return 0;
    }

    // bounds check on X (not required, but maybe 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) {
        SkScalar mid = SkScalarAve(y0, y2);
        // Need [0] and [2] if dir == 1
        // and  [2] and [0] if dir == -1
        xt = y < mid ? pts[1 - dir].fX : pts[dir - 1].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 = SkScalarMulAdd(SkScalarMulAdd(A, t, B), t, C);
    }
    return xt < x ? dir : 0;
}
示例#28
0
void SkEvalQuadAtHalf(const SkPoint src[3], SkPoint* pt, SkVector* tangent) {
    SkASSERT(src);

    if (pt) {
        SkScalar x01 = SkScalarAve(src[0].fX, src[1].fX);
        SkScalar y01 = SkScalarAve(src[0].fY, src[1].fY);
        SkScalar x12 = SkScalarAve(src[1].fX, src[2].fX);
        SkScalar y12 = SkScalarAve(src[1].fY, src[2].fY);
        pt->set(SkScalarAve(x01, x12), SkScalarAve(y01, y12));
    }
    if (tangent) {
        tangent->set(eval_quad_derivative_at_half(&src[0].fX),
                     eval_quad_derivative_at_half(&src[0].fY));
    }
}
示例#29
0
uint32_t GrPathUtils::generateQuadraticPoints(const SkPoint& p0,
                                              const SkPoint& p1,
                                              const SkPoint& p2,
                                              SkScalar tolSqd,
                                              SkPoint** points,
                                              uint32_t pointsLeft) {
    if (pointsLeft < 2 ||
        (p1.distanceToLineSegmentBetweenSqd(p0, p2)) < tolSqd) {
        (*points)[0] = p2;
        *points += 1;
        return 1;
    }

    SkPoint q[] = {
        { SkScalarAve(p0.fX, p1.fX), SkScalarAve(p0.fY, p1.fY) },
        { SkScalarAve(p1.fX, p2.fX), SkScalarAve(p1.fY, p2.fY) },
    };
    SkPoint r = { SkScalarAve(q[0].fX, q[1].fX), SkScalarAve(q[0].fY, q[1].fY) };

    pointsLeft >>= 1;
    uint32_t a = generateQuadraticPoints(p0, q[0], r, tolSqd, points, pointsLeft);
    uint32_t b = generateQuadraticPoints(r, q[1], p2, tolSqd, points, pointsLeft);
    return a + b;
}
示例#30
0
//      midPt sets the first argument to be the midpoint of the other two
//      it is used by quadApprox
static inline void midPt(SkPoint& dest,const SkPoint& a,const SkPoint& b)
{
    dest.set(SkScalarAve(a.fX, b.fX),SkScalarAve(a.fY, b.fY));
}