Home | History | Annotate | Download | only in gradients
      1 /*
      2  * Copyright 2012 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include "SkTwoPointConicalGradient.h"
      9 #include "SkTwoPointConicalGradient_gpu.h"
     10 
     11 struct TwoPtRadialContext {
     12     const TwoPtRadial&  fRec;
     13     float               fRelX, fRelY;
     14     const float         fIncX, fIncY;
     15     float               fB;
     16     const float         fDB;
     17 
     18     TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy,
     19                        SkScalar dfx, SkScalar dfy);
     20     SkFixed nextT();
     21 };
     22 
     23 static int valid_divide(float numer, float denom, float* ratio) {
     24     SkASSERT(ratio);
     25     if (0 == denom) {
     26         return 0;
     27     }
     28     *ratio = numer / denom;
     29     return 1;
     30 }
     31 
     32 // Return the number of distinct real roots, and write them into roots[] in
     33 // ascending order
     34 static int find_quad_roots(float A, float B, float C, float roots[2], bool descendingOrder = false) {
     35     SkASSERT(roots);
     36 
     37     if (A == 0) {
     38         return valid_divide(-C, B, roots);
     39     }
     40 
     41     float R = B*B - 4*A*C;
     42     if (R < 0) {
     43         return 0;
     44     }
     45     R = sk_float_sqrt(R);
     46 
     47 #if 1
     48     float Q = B;
     49     if (Q < 0) {
     50         Q -= R;
     51     } else {
     52         Q += R;
     53     }
     54 #else
     55     // on 10.6 this was much slower than the above branch :(
     56     float Q = B + copysignf(R, B);
     57 #endif
     58     Q *= -0.5f;
     59     if (0 == Q) {
     60         roots[0] = 0;
     61         return 1;
     62     }
     63 
     64     float r0 = Q / A;
     65     float r1 = C / Q;
     66     roots[0] = r0 < r1 ? r0 : r1;
     67     roots[1] = r0 > r1 ? r0 : r1;
     68     if (descendingOrder) {
     69         SkTSwap(roots[0], roots[1]);
     70     }
     71     return 2;
     72 }
     73 
     74 static float lerp(float x, float dx, float t) {
     75     return x + t * dx;
     76 }
     77 
     78 static float sqr(float x) { return x * x; }
     79 
     80 void TwoPtRadial::init(const SkPoint& center0, SkScalar rad0,
     81                        const SkPoint& center1, SkScalar rad1,
     82                        bool flipped) {
     83     fCenterX = SkScalarToFloat(center0.fX);
     84     fCenterY = SkScalarToFloat(center0.fY);
     85     fDCenterX = SkScalarToFloat(center1.fX) - fCenterX;
     86     fDCenterY = SkScalarToFloat(center1.fY) - fCenterY;
     87     fRadius = SkScalarToFloat(rad0);
     88     fDRadius = SkScalarToFloat(rad1) - fRadius;
     89 
     90     fA = sqr(fDCenterX) + sqr(fDCenterY) - sqr(fDRadius);
     91     fRadius2 = sqr(fRadius);
     92     fRDR = fRadius * fDRadius;
     93 
     94     fFlipped = flipped;
     95 }
     96 
     97 TwoPtRadialContext::TwoPtRadialContext(const TwoPtRadial& rec, SkScalar fx, SkScalar fy,
     98                                        SkScalar dfx, SkScalar dfy)
     99     : fRec(rec)
    100     , fRelX(SkScalarToFloat(fx) - rec.fCenterX)
    101     , fRelY(SkScalarToFloat(fy) - rec.fCenterY)
    102     , fIncX(SkScalarToFloat(dfx))
    103     , fIncY(SkScalarToFloat(dfy))
    104     , fB(-2 * (rec.fDCenterX * fRelX + rec.fDCenterY * fRelY + rec.fRDR))
    105     , fDB(-2 * (rec.fDCenterX * fIncX + rec.fDCenterY * fIncY)) {}
    106 
    107 SkFixed TwoPtRadialContext::nextT() {
    108     float roots[2];
    109 
    110     float C = sqr(fRelX) + sqr(fRelY) - fRec.fRadius2;
    111     int countRoots = find_quad_roots(fRec.fA, fB, C, roots, fRec.fFlipped);
    112 
    113     fRelX += fIncX;
    114     fRelY += fIncY;
    115     fB += fDB;
    116 
    117     if (0 == countRoots) {
    118         return TwoPtRadial::kDontDrawT;
    119     }
    120 
    121     // Prefer the bigger t value if both give a radius(t) > 0
    122     // find_quad_roots returns the values sorted, so we start with the last
    123     float t = roots[countRoots - 1];
    124     float r = lerp(fRec.fRadius, fRec.fDRadius, t);
    125     if (r <= 0) {
    126         t = roots[0];   // might be the same as roots[countRoots-1]
    127         r = lerp(fRec.fRadius, fRec.fDRadius, t);
    128         if (r <= 0) {
    129             return TwoPtRadial::kDontDrawT;
    130         }
    131     }
    132     return SkFloatToFixed(t);
    133 }
    134 
    135 typedef void (*TwoPointConicalProc)(TwoPtRadialContext* rec, SkPMColor* dstC,
    136                                     const SkPMColor* cache, int toggle, int count);
    137 
    138 static void twopoint_clamp(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC,
    139                            const SkPMColor* SK_RESTRICT cache, int toggle,
    140                            int count) {
    141     for (; count > 0; --count) {
    142         SkFixed t = rec->nextT();
    143         if (TwoPtRadial::DontDrawT(t)) {
    144             *dstC++ = 0;
    145         } else {
    146             SkFixed index = SkClampMax(t, 0xFFFF);
    147             SkASSERT(index <= 0xFFFF);
    148             *dstC++ = cache[toggle +
    149                             (index >> SkGradientShaderBase::kCache32Shift)];
    150         }
    151         toggle = next_dither_toggle(toggle);
    152     }
    153 }
    154 
    155 static void twopoint_repeat(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC,
    156                             const SkPMColor* SK_RESTRICT cache, int toggle,
    157                             int count) {
    158     for (; count > 0; --count) {
    159         SkFixed t = rec->nextT();
    160         if (TwoPtRadial::DontDrawT(t)) {
    161             *dstC++ = 0;
    162         } else {
    163             SkFixed index = repeat_tileproc(t);
    164             SkASSERT(index <= 0xFFFF);
    165             *dstC++ = cache[toggle +
    166                             (index >> SkGradientShaderBase::kCache32Shift)];
    167         }
    168         toggle = next_dither_toggle(toggle);
    169     }
    170 }
    171 
    172 static void twopoint_mirror(TwoPtRadialContext* rec, SkPMColor* SK_RESTRICT dstC,
    173                             const SkPMColor* SK_RESTRICT cache, int toggle,
    174                             int count) {
    175     for (; count > 0; --count) {
    176         SkFixed t = rec->nextT();
    177         if (TwoPtRadial::DontDrawT(t)) {
    178             *dstC++ = 0;
    179         } else {
    180             SkFixed index = mirror_tileproc(t);
    181             SkASSERT(index <= 0xFFFF);
    182             *dstC++ = cache[toggle +
    183                             (index >> SkGradientShaderBase::kCache32Shift)];
    184         }
    185         toggle = next_dither_toggle(toggle);
    186     }
    187 }
    188 
    189 void SkTwoPointConicalGradient::init() {
    190     fRec.init(fCenter1, fRadius1, fCenter2, fRadius2, fFlippedGrad);
    191     fPtsToUnit.reset();
    192 }
    193 
    194 /////////////////////////////////////////////////////////////////////
    195 
    196 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
    197         const SkPoint& start, SkScalar startRadius,
    198         const SkPoint& end, SkScalar endRadius,
    199         bool flippedGrad, const Descriptor& desc,
    200         const SkMatrix* localMatrix)
    201     : SkGradientShaderBase(desc, localMatrix),
    202     fCenter1(start),
    203     fCenter2(end),
    204     fRadius1(startRadius),
    205     fRadius2(endRadius),
    206     fFlippedGrad(flippedGrad) {
    207     // this is degenerate, and should be caught by our caller
    208     SkASSERT(fCenter1 != fCenter2 || fRadius1 != fRadius2);
    209     this->init();
    210 }
    211 
    212 bool SkTwoPointConicalGradient::isOpaque() const {
    213     // Because areas outside the cone are left untouched, we cannot treat the
    214     // shader as opaque even if the gradient itself is opaque.
    215     // TODO(junov): Compute whether the cone fills the plane crbug.com/222380
    216     return false;
    217 }
    218 
    219 size_t SkTwoPointConicalGradient::contextSize() const {
    220     return sizeof(TwoPointConicalGradientContext);
    221 }
    222 
    223 SkShader::Context* SkTwoPointConicalGradient::onCreateContext(const ContextRec& rec,
    224                                                               void* storage) const {
    225     return SkNEW_PLACEMENT_ARGS(storage, TwoPointConicalGradientContext, (*this, rec));
    226 }
    227 
    228 SkTwoPointConicalGradient::TwoPointConicalGradientContext::TwoPointConicalGradientContext(
    229         const SkTwoPointConicalGradient& shader, const ContextRec& rec)
    230     : INHERITED(shader, rec)
    231 {
    232     // we don't have a span16 proc
    233     fFlags &= ~kHasSpan16_Flag;
    234 
    235     // in general, we might discard based on computed-radius, so clear
    236     // this flag (todo: sometimes we can detect that we never discard...)
    237     fFlags &= ~kOpaqueAlpha_Flag;
    238 }
    239 
    240 void SkTwoPointConicalGradient::TwoPointConicalGradientContext::shadeSpan(
    241         int x, int y, SkPMColor* dstCParam, int count) {
    242     const SkTwoPointConicalGradient& twoPointConicalGradient =
    243             static_cast<const SkTwoPointConicalGradient&>(fShader);
    244 
    245     int toggle = init_dither_toggle(x, y);
    246 
    247     SkASSERT(count > 0);
    248 
    249     SkPMColor* SK_RESTRICT dstC = dstCParam;
    250 
    251     SkMatrix::MapXYProc dstProc = fDstToIndexProc;
    252 
    253     const SkPMColor* SK_RESTRICT cache = fCache->getCache32();
    254 
    255     TwoPointConicalProc shadeProc = twopoint_repeat;
    256     if (SkShader::kClamp_TileMode == twoPointConicalGradient.fTileMode) {
    257         shadeProc = twopoint_clamp;
    258     } else if (SkShader::kMirror_TileMode == twoPointConicalGradient.fTileMode) {
    259         shadeProc = twopoint_mirror;
    260     } else {
    261         SkASSERT(SkShader::kRepeat_TileMode == twoPointConicalGradient.fTileMode);
    262     }
    263 
    264     if (fDstToIndexClass != kPerspective_MatrixClass) {
    265         SkPoint srcPt;
    266         dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
    267                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
    268         SkScalar dx, fx = srcPt.fX;
    269         SkScalar dy, fy = srcPt.fY;
    270 
    271         if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
    272             SkFixed fixedX, fixedY;
    273             (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
    274             dx = SkFixedToScalar(fixedX);
    275             dy = SkFixedToScalar(fixedY);
    276         } else {
    277             SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
    278             dx = fDstToIndex.getScaleX();
    279             dy = fDstToIndex.getSkewY();
    280         }
    281 
    282         TwoPtRadialContext rec(twoPointConicalGradient.fRec, fx, fy, dx, dy);
    283         (*shadeProc)(&rec, dstC, cache, toggle, count);
    284     } else {    // perspective case
    285         SkScalar dstX = SkIntToScalar(x) + SK_ScalarHalf;
    286         SkScalar dstY = SkIntToScalar(y) + SK_ScalarHalf;
    287         for (; count > 0; --count) {
    288             SkPoint srcPt;
    289             dstProc(fDstToIndex, dstX, dstY, &srcPt);
    290             TwoPtRadialContext rec(twoPointConicalGradient.fRec, srcPt.fX, srcPt.fY, 0, 0);
    291             (*shadeProc)(&rec, dstC, cache, toggle, 1);
    292 
    293             dstX += SK_Scalar1;
    294             toggle = next_dither_toggle(toggle);
    295             dstC += 1;
    296         }
    297     }
    298 }
    299 
    300 SkShader::BitmapType SkTwoPointConicalGradient::asABitmap(
    301     SkBitmap* bitmap, SkMatrix* matrix, SkShader::TileMode* xy) const {
    302     SkPoint diff = fCenter2 - fCenter1;
    303     SkScalar diffLen = 0;
    304 
    305     if (bitmap) {
    306         this->getGradientTableBitmap(bitmap);
    307     }
    308     if (matrix) {
    309         diffLen = diff.length();
    310     }
    311     if (matrix) {
    312         if (diffLen) {
    313             SkScalar invDiffLen = SkScalarInvert(diffLen);
    314             // rotate to align circle centers with the x-axis
    315             matrix->setSinCos(-SkScalarMul(invDiffLen, diff.fY),
    316                               SkScalarMul(invDiffLen, diff.fX));
    317         } else {
    318             matrix->reset();
    319         }
    320         matrix->preTranslate(-fCenter1.fX, -fCenter1.fY);
    321     }
    322     if (xy) {
    323         xy[0] = fTileMode;
    324         xy[1] = kClamp_TileMode;
    325     }
    326     return kTwoPointConical_BitmapType;
    327 }
    328 
    329 // Returns the original non-sorted version of the gradient
    330 SkShader::GradientType SkTwoPointConicalGradient::asAGradient(
    331     GradientInfo* info) const {
    332     if (info) {
    333         commonAsAGradient(info, fFlippedGrad);
    334         info->fPoint[0] = fCenter1;
    335         info->fPoint[1] = fCenter2;
    336         info->fRadius[0] = fRadius1;
    337         info->fRadius[1] = fRadius2;
    338         if (fFlippedGrad) {
    339             SkTSwap(info->fPoint[0], info->fPoint[1]);
    340             SkTSwap(info->fRadius[0], info->fRadius[1]);
    341         }
    342     }
    343     return kConical_GradientType;
    344 }
    345 
    346 SkTwoPointConicalGradient::SkTwoPointConicalGradient(
    347     SkReadBuffer& buffer)
    348     : INHERITED(buffer),
    349     fCenter1(buffer.readPoint()),
    350     fCenter2(buffer.readPoint()),
    351     fRadius1(buffer.readScalar()),
    352     fRadius2(buffer.readScalar()) {
    353     if (buffer.isVersionLT(SkReadBuffer::kGradientFlippedFlag_Version)) {
    354         // V23_COMPATIBILITY_CODE
    355         // Sort gradient by radius size for old pictures
    356         if (fRadius2 < fRadius1) {
    357             SkTSwap(fCenter1, fCenter2);
    358             SkTSwap(fRadius1, fRadius2);
    359             this->flipGradientColors();
    360             fFlippedGrad = true;
    361         } else {
    362             fFlippedGrad = false;
    363         }
    364     } else {
    365         fFlippedGrad = buffer.readBool();
    366     }
    367     this->init();
    368 };
    369 
    370 void SkTwoPointConicalGradient::flatten(
    371     SkWriteBuffer& buffer) const {
    372     this->INHERITED::flatten(buffer);
    373     buffer.writePoint(fCenter1);
    374     buffer.writePoint(fCenter2);
    375     buffer.writeScalar(fRadius1);
    376     buffer.writeScalar(fRadius2);
    377     buffer.writeBool(fFlippedGrad);
    378 }
    379 
    380 #if SK_SUPPORT_GPU
    381 
    382 #include "SkGr.h"
    383 
    384 bool SkTwoPointConicalGradient::asNewEffect(GrContext* context, const SkPaint& paint,
    385                                              const SkMatrix* localMatrix, GrColor* grColor,
    386                                              GrEffectRef** grEffect)  const {
    387     SkASSERT(NULL != context);
    388     SkASSERT(fPtsToUnit.isIdentity());
    389 
    390     *grEffect = Gr2PtConicalGradientEffect::Create(context, *this, fTileMode, localMatrix);
    391     *grColor = SkColor2GrColorJustAlpha(paint.getColor());
    392     return true;
    393 }
    394 
    395 #else
    396 
    397 bool SkTwoPointConicalGradient::asNewEffect(GrContext* context, const SkPaint& paint,
    398                                             const SkMatrix* localMatrix, GrColor* grColor,
    399                                             GrEffectRef** grEffect)  const {
    400     SkDEBUGFAIL("Should not call in GPU-less build");
    401     return false;
    402 }
    403 
    404 #endif
    405 
    406 #ifndef SK_IGNORE_TO_STRING
    407 void SkTwoPointConicalGradient::toString(SkString* str) const {
    408     str->append("SkTwoPointConicalGradient: (");
    409 
    410     str->append("center1: (");
    411     str->appendScalar(fCenter1.fX);
    412     str->append(", ");
    413     str->appendScalar(fCenter1.fY);
    414     str->append(") radius1: ");
    415     str->appendScalar(fRadius1);
    416     str->append(" ");
    417 
    418     str->append("center2: (");
    419     str->appendScalar(fCenter2.fX);
    420     str->append(", ");
    421     str->appendScalar(fCenter2.fY);
    422     str->append(") radius2: ");
    423     str->appendScalar(fRadius2);
    424     str->append(" ");
    425 
    426     this->INHERITED::toString(str);
    427 
    428     str->append(")");
    429 }
    430 #endif
    431