/*
 * 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 "SkMatrix.h"
#include "SkRRect.h"
#include "Test.h"

static void test_tricky_radii(skiatest::Reporter* reporter) {
    {
        // crbug.com/458522
        SkRRect rr;
        const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
        const SkScalar rad = 12814;
        const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
        rr.setRectRadii(bounds, vec);
    }

    {
        // crbug.com//463920
        SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
        SkVector radii[4] = {
            { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
        };
        SkRRect rr;
        rr.setRectRadii(r, radii);

        REPORTER_ASSERT(reporter, (double) rr.radii(SkRRect::kUpperRight_Corner).fY +
                                  (double) rr.radii(SkRRect::kLowerRight_Corner).fY <=
                                  rr.height());
    }
}

static void test_empty_crbug_458524(skiatest::Reporter* reporter) {
    SkRRect rr;
    const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
    const SkScalar rad = 40;
    rr.setRectXY(bounds, rad, rad);

    SkRRect other;
    SkMatrix matrix;
    matrix.setScale(0, 1);
    rr.transform(matrix, &other);
    REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == other.getType());
}

static const SkScalar kWidth = 100.0f;
static const SkScalar kHeight = 100.0f;

static void test_inset(skiatest::Reporter* reporter) {
    SkRRect rr, rr2;
    SkRect r = { 0, 0, 100, 100 };

    rr.setRect(r);
    rr.inset(-20, -20, &rr2);
    REPORTER_ASSERT(reporter, rr2.isRect());

    rr.inset(20, 20, &rr2);
    REPORTER_ASSERT(reporter, rr2.isRect());

    rr.inset(r.width()/2, r.height()/2, &rr2);
    REPORTER_ASSERT(reporter, rr2.isEmpty());

    rr.setRectXY(r, 20, 20);
    rr.inset(19, 19, &rr2);
    REPORTER_ASSERT(reporter, rr2.isSimple());
    rr.inset(20, 20, &rr2);
    REPORTER_ASSERT(reporter, rr2.isRect());
}


static void test_9patch_rrect(skiatest::Reporter* reporter,
                              const SkRect& rect,
                              SkScalar l, SkScalar t, SkScalar r, SkScalar b,
                              bool checkRadii) {
    SkRRect rr;
    rr.setNinePatch(rect, l, t, r, b);

    REPORTER_ASSERT(reporter, SkRRect::kNinePatch_Type == rr.type());
    REPORTER_ASSERT(reporter, rr.rect() == rect);

    if (checkRadii) {
        // This test doesn't hold if the radii will be rescaled by SkRRect
        SkRect ninePatchRadii = { l, t, r, b };
        SkPoint rquad[4];
        ninePatchRadii.toQuad(rquad);
        for (int i = 0; i < 4; ++i) {
            REPORTER_ASSERT(reporter, rquad[i] == rr.radii((SkRRect::Corner) i));
        }
    }
    SkRRect rr2; // construct the same RR using the most general set function
    SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
    rr2.setRectRadii(rect, radii);
    REPORTER_ASSERT(reporter, rr2 == rr && rr2.getType() == rr.getType());
}

// Test out the basic API entry points
static void test_round_rect_basic(skiatest::Reporter* reporter) {
    // Test out initialization methods
    SkPoint zeroPt = { 0, 0 };
    SkRRect empty;

    empty.setEmpty();

    REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type());
    REPORTER_ASSERT(reporter, empty.rect().isEmpty());

    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter, zeroPt == empty.radii((SkRRect::Corner) i));
    }

    //----
    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);

    SkRRect rr1;
    rr1.setRect(rect);

    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type());
    REPORTER_ASSERT(reporter, rr1.rect() == rect);

    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter, zeroPt == rr1.radii((SkRRect::Corner) i));
    }
    SkRRect rr1_2; // construct the same RR using the most general set function
    SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
    rr1_2.setRectRadii(rect, rr1_2_radii);
    REPORTER_ASSERT(reporter, rr1_2 == rr1 && rr1_2.getType() == rr1.getType());
    SkRRect rr1_3;  // construct the same RR using the nine patch set function
    rr1_3.setNinePatch(rect, 0, 0, 0, 0);
    REPORTER_ASSERT(reporter, rr1_3 == rr1 && rr1_3.getType() == rr1.getType());

    //----
    SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
    SkRRect rr2;
    rr2.setOval(rect);

    REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr2.type());
    REPORTER_ASSERT(reporter, rr2.rect() == rect);

    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter,
                        rr2.radii((SkRRect::Corner) i).equalsWithinTolerance(halfPoint));
    }
    SkRRect rr2_2;  // construct the same RR using the most general set function
    SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY },
                                { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY } };
    rr2_2.setRectRadii(rect, rr2_2_radii);
    REPORTER_ASSERT(reporter, rr2_2 == rr2 && rr2_2.getType() == rr2.getType());
    SkRRect rr2_3;  // construct the same RR using the nine patch set function
    rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
    REPORTER_ASSERT(reporter, rr2_3 == rr2 && rr2_3.getType() == rr2.getType());

    //----
    SkPoint p = { 5, 5 };
    SkRRect rr3;
    rr3.setRectXY(rect, p.fX, p.fY);

    REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr3.type());
    REPORTER_ASSERT(reporter, rr3.rect() == rect);

    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter, p == rr3.radii((SkRRect::Corner) i));
    }
    SkRRect rr3_2; // construct the same RR using the most general set function
    SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
    rr3_2.setRectRadii(rect, rr3_2_radii);
    REPORTER_ASSERT(reporter, rr3_2 == rr3 && rr3_2.getType() == rr3.getType());
    SkRRect rr3_3;  // construct the same RR using the nine patch set function
    rr3_3.setNinePatch(rect, 5, 5, 5, 5);
    REPORTER_ASSERT(reporter, rr3_3 == rr3 && rr3_3.getType() == rr3.getType());

    //----
    test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);

    {
        // Test out the rrect from skia:3466
        SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f, 0.806214333f);

        test_9patch_rrect(reporter,
                          rect2,
                          0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
                          false);
    }

    //----
    SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };

    SkRRect rr5;
    rr5.setRectRadii(rect, radii2);

    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr5.type());
    REPORTER_ASSERT(reporter, rr5.rect() == rect);

    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter, radii2[i] == rr5.radii((SkRRect::Corner) i));
    }

    // Test out == & !=
    REPORTER_ASSERT(reporter, empty != rr3);
    REPORTER_ASSERT(reporter, rr3 != rr5);
}

// Test out the cases when the RR degenerates to a rect
static void test_round_rect_rects(skiatest::Reporter* reporter) {
    SkRect r;

    //----
    SkRRect empty;

    empty.setEmpty();

    REPORTER_ASSERT(reporter, SkRRect::kEmpty_Type == empty.type());
    r = empty.rect();
    REPORTER_ASSERT(reporter, 0 == r.fLeft && 0 == r.fTop && 0 == r.fRight && 0 == r.fBottom);

    //----
    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    SkRRect rr1;
    rr1.setRectXY(rect, 0, 0);

    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr1.type());
    r = rr1.rect();
    REPORTER_ASSERT(reporter, rect == r);

    //----
    SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };

    SkRRect rr2;
    rr2.setRectRadii(rect, radii);

    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr2.type());
    r = rr2.rect();
    REPORTER_ASSERT(reporter, rect == r);

    //----
    SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };

    SkRRect rr3;
    rr3.setRectRadii(rect, radii2);
    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr3.type());
}

// Test out the cases when the RR degenerates to an oval
static void test_round_rect_ovals(skiatest::Reporter* reporter) {
    //----
    SkRect oval;
    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    SkRRect rr1;
    rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));

    REPORTER_ASSERT(reporter, SkRRect::kOval_Type == rr1.type());
    oval = rr1.rect();
    REPORTER_ASSERT(reporter, oval == rect);
}

// Test out the non-degenerate RR cases
static void test_round_rect_general(skiatest::Reporter* reporter) {
    //----
    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    SkRRect rr1;
    rr1.setRectXY(rect, 20, 20);

    REPORTER_ASSERT(reporter, SkRRect::kSimple_Type == rr1.type());

    //----
    SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };

    SkRRect rr2;
    rr2.setRectRadii(rect, radii);

    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr2.type());
}

// Test out questionable-parameter handling
static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {

    // When the radii exceed the base rect they are proportionally scaled down
    // to fit
    SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
    SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };

    SkRRect rr1;
    rr1.setRectRadii(rect, radii);

    REPORTER_ASSERT(reporter, SkRRect::kComplex_Type == rr1.type());

    const SkPoint& p = rr1.radii(SkRRect::kUpperLeft_Corner);

    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fX, 33.33333f));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(p.fY, 66.66666f));

    // Negative radii should be capped at zero
    SkRRect rr2;
    rr2.setRectXY(rect, -10, -20);

    REPORTER_ASSERT(reporter, SkRRect::kRect_Type == rr2.type());

    const SkPoint& p2 = rr2.radii(SkRRect::kUpperLeft_Corner);

    REPORTER_ASSERT(reporter, 0.0f == p2.fX);
    REPORTER_ASSERT(reporter, 0.0f == p2.fY);
}

// Move a small box from the start position by (stepX, stepY) 'numSteps' times
// testing for containment in 'rr' at each step.
static void test_direction(skiatest::Reporter* reporter, const SkRRect &rr,
                           SkScalar initX, int stepX, SkScalar initY, int stepY,
                           int numSteps, const bool* contains) {
    SkScalar x = initX, y = initY;
    for (int i = 0; i < numSteps; ++i) {
        SkRect test = SkRect::MakeXYWH(x, y,
                                       stepX ? SkIntToScalar(stepX) : SK_Scalar1,
                                       stepY ? SkIntToScalar(stepY) : SK_Scalar1);
        test.sort();

        REPORTER_ASSERT(reporter, contains[i] == rr.contains(test));

        x += stepX;
        y += stepY;
    }
}

// Exercise the RR's contains rect method
static void test_round_rect_contains_rect(skiatest::Reporter* reporter) {

    static const int kNumRRects = 4;
    static const SkVector gRadii[kNumRRects][4] = {
        { {  0,  0 }, {  0,  0 }, {  0,  0 }, {  0,  0 } },  // rect
        { { 20, 20 }, { 20, 20 }, { 20, 20 }, { 20, 20 } },  // circle
        { { 10, 10 }, { 10, 10 }, { 10, 10 }, { 10, 10 } },  // simple
        { {  0,  0 }, { 20, 20 }, { 10, 10 }, { 30, 30 } }   // complex
    };

    SkRRect rrects[kNumRRects];
    for (int i = 0; i < kNumRRects; ++i) {
        rrects[i].setRectRadii(SkRect::MakeWH(40, 40), gRadii[i]);
    }

    // First test easy outs - boxes that are obviously out on
    // each corner and edge
    static const SkRect easyOuts[] = {
        { -5, -5,  5,  5 }, // NW
        { 15, -5, 20,  5 }, // N
        { 35, -5, 45,  5 }, // NE
        { 35, 15, 45, 20 }, // E
        { 35, 45, 35, 45 }, // SE
        { 15, 35, 20, 45 }, // S
        { -5, 35,  5, 45 }, // SW
        { -5, 15,  5, 20 }  // W
    };

    for (int i = 0; i < kNumRRects; ++i) {
        for (size_t j = 0; j < SK_ARRAY_COUNT(easyOuts); ++j) {
            REPORTER_ASSERT(reporter, !rrects[i].contains(easyOuts[j]));
        }
    }

    // Now test non-trivial containment. For each compass
    // point walk a 1x1 rect in from the edge  of the bounding
    // rect
    static const int kNumSteps = 15;
    bool answers[kNumRRects][8][kNumSteps] = {
        // all the test rects are inside the degenerate rrect
        {
            // rect
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        },
        // for the circle we expect 6 blocks to be out on the
        // corners (then the rest in) and only the first block
        // out on the vertical and horizontal axes (then
        // the rest in)
        {
            // circle
            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        },
        // for the simple round rect we expect 3 out on
        // the corners (then the rest in) and no blocks out
        // on the vertical and horizontal axes
        {
            // simple RR
            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
        },
        // for the complex case the answer is different for each direction
        {
            // complex RR
            // all in for NW (rect) corner (same as rect case)
            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            // only first block out for N (same as circle case)
            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            // first 6 blocks out for NE (same as circle case)
            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            // only first block out for E (same as circle case)
            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            // first 3 blocks out for SE (same as simple case)
            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            // first two blocks out for S
            { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
            // first 9 blocks out for SW
            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 },
            // first two blocks out for W (same as S)
            { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
         }
    };

    for (int i = 0; i < kNumRRects; ++i) {
        test_direction(reporter, rrects[i],     0,  1,     0,  1, kNumSteps, answers[i][0]); // NW
        test_direction(reporter, rrects[i], 19.5f,  0,     0,  1, kNumSteps, answers[i][1]); // N
        test_direction(reporter, rrects[i],    40, -1,     0,  1, kNumSteps, answers[i][2]); // NE
        test_direction(reporter, rrects[i],    40, -1, 19.5f,  0, kNumSteps, answers[i][3]); // E
        test_direction(reporter, rrects[i],    40, -1,    40, -1, kNumSteps, answers[i][4]); // SE
        test_direction(reporter, rrects[i], 19.5f,  0,    40, -1, kNumSteps, answers[i][5]); // S
        test_direction(reporter, rrects[i],     0,  1,    40, -1, kNumSteps, answers[i][6]); // SW
        test_direction(reporter, rrects[i],     0,  1, 19.5f,  0, kNumSteps, answers[i][7]); // W
    }
}

// Called for a matrix that should cause SkRRect::transform to fail.
static void assert_transform_failure(skiatest::Reporter* reporter, const SkRRect& orig,
                                     const SkMatrix& matrix) {
    // The test depends on the fact that the original is not empty.
    SkASSERT(!orig.isEmpty());
    SkRRect dst;
    dst.setEmpty();

    const SkRRect copyOfDst = dst;
    const SkRRect copyOfOrig = orig;
    bool success = orig.transform(matrix, &dst);
    // This transform should fail.
    REPORTER_ASSERT(reporter, !success);
    // Since the transform failed, dst should be unchanged.
    REPORTER_ASSERT(reporter, copyOfDst == dst);
    // original should not be modified.
    REPORTER_ASSERT(reporter, copyOfOrig == orig);
    REPORTER_ASSERT(reporter, orig != dst);
}

#define GET_RADII                                                       \
    const SkVector& origUL = orig.radii(SkRRect::kUpperLeft_Corner);    \
    const SkVector& origUR = orig.radii(SkRRect::kUpperRight_Corner);   \
    const SkVector& origLR = orig.radii(SkRRect::kLowerRight_Corner);   \
    const SkVector& origLL = orig.radii(SkRRect::kLowerLeft_Corner);    \
    const SkVector& dstUL = dst.radii(SkRRect::kUpperLeft_Corner);      \
    const SkVector& dstUR = dst.radii(SkRRect::kUpperRight_Corner);     \
    const SkVector& dstLR = dst.radii(SkRRect::kLowerRight_Corner);     \
    const SkVector& dstLL = dst.radii(SkRRect::kLowerLeft_Corner)

// Called to test various transforms on a single SkRRect.
static void test_transform_helper(skiatest::Reporter* reporter, const SkRRect& orig) {
    SkRRect dst;
    dst.setEmpty();

    // The identity matrix will duplicate the rrect.
    bool success = orig.transform(SkMatrix::I(), &dst);
    REPORTER_ASSERT(reporter, success);
    REPORTER_ASSERT(reporter, orig == dst);

    // Skew and Perspective make transform fail.
    SkMatrix matrix;
    matrix.reset();
    matrix.setSkewX(SkIntToScalar(2));
    assert_transform_failure(reporter, orig, matrix);

    matrix.reset();
    matrix.setSkewY(SkIntToScalar(3));
    assert_transform_failure(reporter, orig, matrix);

    matrix.reset();
    matrix.setPerspX(4);
    assert_transform_failure(reporter, orig, matrix);

    matrix.reset();
    matrix.setPerspY(5);
    assert_transform_failure(reporter, orig, matrix);

    // Rotation fails.
    matrix.reset();
    matrix.setRotate(SkIntToScalar(90));
    assert_transform_failure(reporter, orig, matrix);
    matrix.setRotate(SkIntToScalar(37));
    assert_transform_failure(reporter, orig, matrix);

    // Translate will keep the rect moved, but otherwise the same.
    matrix.reset();
    SkScalar translateX = SkIntToScalar(32);
    SkScalar translateY = SkIntToScalar(15);
    matrix.setTranslateX(translateX);
    matrix.setTranslateY(translateY);
    dst.setEmpty();
    success = orig.transform(matrix, &dst);
    REPORTER_ASSERT(reporter, success);
    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter,
                orig.radii((SkRRect::Corner) i) == dst.radii((SkRRect::Corner) i));
    }
    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
    REPORTER_ASSERT(reporter, dst.rect().left() == orig.rect().left() + translateX);
    REPORTER_ASSERT(reporter, dst.rect().top() == orig.rect().top() + translateY);

    // Keeping the translation, but adding skew will make transform fail.
    matrix.setSkewY(SkIntToScalar(7));
    assert_transform_failure(reporter, orig, matrix);

    // Scaling in -x will flip the round rect horizontally.
    matrix.reset();
    matrix.setScaleX(SkIntToScalar(-1));
    dst.setEmpty();
    success = orig.transform(matrix, &dst);
    REPORTER_ASSERT(reporter, success);
    {
        GET_RADII;
        // Radii have swapped in x.
        REPORTER_ASSERT(reporter, origUL == dstUR);
        REPORTER_ASSERT(reporter, origUR == dstUL);
        REPORTER_ASSERT(reporter, origLR == dstLL);
        REPORTER_ASSERT(reporter, origLL == dstLR);
    }
    // Width and height remain the same.
    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
    // Right and left have swapped (sort of)
    REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());
    // Top has stayed the same.
    REPORTER_ASSERT(reporter, orig.rect().top() == dst.rect().top());

    // Keeping the scale, but adding a persp will make transform fail.
    matrix.setPerspX(7);
    assert_transform_failure(reporter, orig, matrix);

    // Scaling in -y will flip the round rect vertically.
    matrix.reset();
    matrix.setScaleY(SkIntToScalar(-1));
    dst.setEmpty();
    success = orig.transform(matrix, &dst);
    REPORTER_ASSERT(reporter, success);
    {
        GET_RADII;
        // Radii have swapped in y.
        REPORTER_ASSERT(reporter, origUL == dstLL);
        REPORTER_ASSERT(reporter, origUR == dstLR);
        REPORTER_ASSERT(reporter, origLR == dstUR);
        REPORTER_ASSERT(reporter, origLL == dstUL);
    }
    // Width and height remain the same.
    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
    // Top and bottom have swapped (sort of)
    REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
    // Left has stayed the same.
    REPORTER_ASSERT(reporter, orig.rect().left() == dst.rect().left());

    // Scaling in -x and -y will swap in both directions.
    matrix.reset();
    matrix.setScaleY(SkIntToScalar(-1));
    matrix.setScaleX(SkIntToScalar(-1));
    dst.setEmpty();
    success = orig.transform(matrix, &dst);
    REPORTER_ASSERT(reporter, success);
    {
        GET_RADII;
        REPORTER_ASSERT(reporter, origUL == dstLR);
        REPORTER_ASSERT(reporter, origUR == dstLL);
        REPORTER_ASSERT(reporter, origLR == dstUL);
        REPORTER_ASSERT(reporter, origLL == dstUR);
    }
    // Width and height remain the same.
    REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
    REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
    REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
    REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());

    // Scale in both directions.
    SkScalar xScale = SkIntToScalar(3);
    SkScalar yScale = 3.2f;
    matrix.reset();
    matrix.setScaleX(xScale);
    matrix.setScaleY(yScale);
    dst.setEmpty();
    success = orig.transform(matrix, &dst);
    REPORTER_ASSERT(reporter, success);
    // Radii are scaled.
    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fX,
                                    SkScalarMul(orig.radii((SkRRect::Corner) i).fX, xScale)));
        REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.radii((SkRRect::Corner) i).fY,
                                    SkScalarMul(orig.radii((SkRRect::Corner) i).fY, yScale)));
    }
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().width(),
                                                  SkScalarMul(orig.rect().width(), xScale)));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().height(),
                                                  SkScalarMul(orig.rect().height(), yScale)));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().left(),
                                                  SkScalarMul(orig.rect().left(), xScale)));
    REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().top(),
                                                  SkScalarMul(orig.rect().top(), yScale)));
}

static void test_round_rect_transform(skiatest::Reporter* reporter) {
    SkRRect rrect;
    {
        SkRect r = { 0, 0, kWidth, kHeight };
        rrect.setRectXY(r, SkIntToScalar(4), SkIntToScalar(7));
        test_transform_helper(reporter, rrect);
    }
    {
        SkRect r = { SkIntToScalar(5), SkIntToScalar(15),
                     SkIntToScalar(27), SkIntToScalar(34) };
        SkVector radii[4] = { { 0, SkIntToScalar(1) },
                              { SkIntToScalar(2), SkIntToScalar(3) },
                              { SkIntToScalar(4), SkIntToScalar(5) },
                              { SkIntToScalar(6), SkIntToScalar(7) } };
        rrect.setRectRadii(r, radii);
        test_transform_helper(reporter, rrect);
    }
}

// Test out the case where an oval already off in space is translated/scaled 
// further off into space - yielding numerical issues when the rect & radii
// are transformed separatly
// BUG=skia:2696
static void test_issue_2696(skiatest::Reporter* reporter) {
    SkRRect rrect;
    SkRect r = { 28443.8594f, 53.1428604f, 28446.7148f, 56.0000038f };
    rrect.setOval(r);

    SkMatrix xform;
    xform.setAll(2.44f,  0.0f, 485411.7f,
                 0.0f,  2.44f,   -438.7f,
                 0.0f,   0.0f,      1.0f);
    SkRRect dst;

    bool success = rrect.transform(xform, &dst);
    REPORTER_ASSERT(reporter, success);

    SkScalar halfWidth = SkScalarHalf(dst.width());
    SkScalar halfHeight = SkScalarHalf(dst.height());

    for (int i = 0; i < 4; ++i) {
        REPORTER_ASSERT(reporter, 
                        SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fX, halfWidth));
        REPORTER_ASSERT(reporter, 
                        SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fY, halfHeight));
    }
}

DEF_TEST(RoundRect, reporter) {
    test_round_rect_basic(reporter);
    test_round_rect_rects(reporter);
    test_round_rect_ovals(reporter);
    test_round_rect_general(reporter);
    test_round_rect_iffy_parameters(reporter);
    test_inset(reporter);
    test_round_rect_contains_rect(reporter);
    test_round_rect_transform(reporter);
    test_issue_2696(reporter);
    test_tricky_radii(reporter);
    test_empty_crbug_458524(reporter);
}