text: /*
   * Copyright 2006 The Android Open Source Project
   *
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  
  #include "include/core/SkColorSpace.h"
  #include "include/core/SkMallocPixelRef.h"
  #include "include/private/SkFloatBits.h"
  #include "include/private/SkHalf.h"
  #include "include/private/SkTPin.h"
  #include "include/private/SkVx.h"
  #include "src/core/SkColorSpacePriv.h"
  #include "src/core/SkConvertPixels.h"
  #include "src/core/SkMatrixProvider.h"
  #include "src/core/SkReadBuffer.h"
  #include "src/core/SkVM.h"
  #include "src/core/SkWriteBuffer.h"
  #include "src/shaders/gradients/Sk4fLinearGradient.h"
  #include "src/shaders/gradients/SkGradientShaderPriv.h"
  #include "src/shaders/gradients/SkLinearGradient.h"
  #include "src/shaders/gradients/SkRadialGradient.h"
  #include "src/shaders/gradients/SkSweepGradient.h"
  #include "src/shaders/gradients/SkTwoPointConicalGradient.h"
  
  #include <algorithm>
  
  enum GradientSerializationFlags {
      // Bits 29:31 used for various boolean flags
      kHasPosition_GSF    = 0x80000000,
      kHasLocalMatrix_GSF = 0x40000000,
      kHasColorSpace_GSF  = 0x20000000,
  
      // Bits 12:28 unused
  
      // Bits 8:11 for fTileMode
      kTileModeShift_GSF  = 8,
      kTileModeMask_GSF   = 0xF,
  
      // Bits 0:7 for fGradFlags (note that kForce4fContext_PrivateFlag is 0x80)
      kGradFlagsShift_GSF = 0,
      kGradFlagsMask_GSF  = 0xFF,
  };
  
  SkGradientShaderBase::Descriptor::Descriptor() {
      sk_bzero(this, sizeof(*this));
      fTileMode = SkTileMode::kClamp;
  }
  SkGradientShaderBase::Descriptor::~Descriptor() = default;
  
  void SkGradientShaderBase::Descriptor::flatten(SkWriteBuffer& buffer) const {
      uint32_t flags = 0;
      if (fPos) {
          flags |= kHasPosition_GSF;
      }
      if (fLocalMatrix) {
          flags |= kHasLocalMatrix_GSF;
      }
      sk_sp<SkData> colorSpaceData = fColorSpace ? fColorSpace->serialize() : nullptr;
      if (colorSpaceData) {
          flags |= kHasColorSpace_GSF;
      }
      SkASSERT(static_cast<uint32_t>(fTileMode) <= kTileModeMask_GSF);
      flags |= ((unsigned)fTileMode << kTileModeShift_GSF);
      SkASSERT(fGradFlags <= kGradFlagsMask_GSF);
      flags |= (fGradFlags << kGradFlagsShift_GSF);
  
      buffer.writeUInt(flags);
  
      buffer.writeColor4fArray(fColors, fCount);
      if (colorSpaceData) {
          buffer.writeDataAsByteArray(colorSpaceData.get());
      }
      if (fPos) {
          buffer.writeScalarArray(fPos, fCount);
      }
      if (fLocalMatrix) {
          buffer.writeMatrix(*fLocalMatrix);
      }
  }
  
  template <int N, typename T, bool MEM_MOVE>
  static bool validate_array(SkReadBuffer& buffer, size_t count, SkSTArray<N, T, MEM_MOVE>* array) {
      if (!buffer.validateCanReadN<T>(count)) {
          return false;
      }
  
      array->resize_back(count);
      return true;
  }
  
  bool SkGradientShaderBase::DescriptorScope::unflatten(SkReadBuffer& buffer) {
      // New gradient format. Includes floating point color, color space, densely packed flags
      uint32_t flags = buffer.readUInt();
  
      fTileMode = (SkTileMode)((flags >> kTileModeShift_GSF) & kTileModeMask_GSF);
      fGradFlags = (flags >> kGradFlagsShift_GSF) & kGradFlagsMask_GSF;
  
      fCount = buffer.getArrayCount();
  
      if (!(validate_array(buffer, fCount, &fColorStorage) &&
            buffer.readColor4fArray(fColorStorage.begin(), fCount))) {
          return false;
      }
      fColors = fColorStorage.begin();
  
      if (SkToBool(flags & kHasColorSpace_GSF)) {
          sk_sp<SkData> data = buffer.readByteArrayAsData();
          fColorSpace = data ? SkColorSpace::Deserialize(data->data(), data->size()) : nullptr;
      } else {
          fColorSpace = nullptr;
      }
      if (SkToBool(flags & kHasPosition_GSF)) {
          if (!(validate_array(buffer, fCount, &fPosStorage) &&
                buffer.readScalarArray(fPosStorage.begin(), fCount))) {
              return false;
          }
          fPos = fPosStorage.begin();
      } else {
          fPos = nullptr;
      }
      if (SkToBool(flags & kHasLocalMatrix_GSF)) {
          fLocalMatrix = &fLocalMatrixStorage;
          buffer.readMatrix(&fLocalMatrixStorage);
      } else {
          fLocalMatrix = nullptr;
      }
      return buffer.isValid();
  }
  
  ////////////////////////////////////////////////////////////////////////////////////////////
  
  SkGradientShaderBase::SkGradientShaderBase(const Descriptor& desc, const SkMatrix& ptsToUnit)
      : INHERITED(desc.fLocalMatrix)
      , fPtsToUnit(ptsToUnit)
      , fColorSpace(desc.fColorSpace ? desc.fColorSpace : SkColorSpace::MakeSRGB())
      , fColorsAreOpaque(true)
  {
      fPtsToUnit.getType();  // Precache so reads are threadsafe.
      SkASSERT(desc.fCount > 1);
  
      fGradFlags = static_cast<uint8_t>(desc.fGradFlags);
  
      SkASSERT((unsigned)desc.fTileMode < kSkTileModeCount);
      fTileMode = desc.fTileMode;
  
      /*  Note: we let the caller skip the first and/or last position.
          i.e. pos[0] = 0.3, pos[1] = 0.7
          In these cases, we insert entries to ensure that the final data
          will be bracketed by [0, 1].
          i.e. our_pos[0] = 0, our_pos[1] = 0.3, our_pos[2] = 0.7, our_pos[3] = 1
  
          Thus colorCount (the caller's value, and fColorCount (our value) may
          differ by up to 2. In the above example:
              colorCount = 2
              fColorCount = 4
       */
      fColorCount = desc.fCount;
      // check if we need to add in start and/or end position/colors
      bool needsFirst = false;
      bool needsLast = false;
      if (desc.fPos) {
          needsFirst = desc.fPos[0] != 0;
          needsLast = desc.fPos[desc.fCount - 1] != SK_Scalar1;
          fColorCount += needsFirst + needsLast;
      }
  
      size_t storageSize = fColorCount * (sizeof(SkColor4f) + (desc.fPos ? sizeof(SkScalar) : 0));
      fOrigColors4f      = reinterpret_cast<SkColor4f*>(fStorage.reset(storageSize));
      fOrigPos           = desc.fPos ? reinterpret_cast<SkScalar*>(fOrigColors4f + fColorCount)
                                     : nullptr;
  
      // Now copy over the colors, adding the dummies as needed
      SkColor4f* origColors = fOrigColors4f;
      if (needsFirst) {
          *origColors++ = desc.fColors[0];
      }
      for (int i = 0; i < desc.fCount; ++i) {
          origColors[i] = desc.fColors[i];
          fColorsAreOpaque = fColorsAreOpaque && (desc.fColors[i].fA == 1);
      }
      if (needsLast) {
          origColors += desc.fCount;
          *origColors = desc.fColors[desc.fCount - 1];
      }
  
      if (desc.fPos) {
          SkScalar prev = 0;
          SkScalar* origPosPtr = fOrigPos;
          *origPosPtr++ = prev; // force the first pos to 0
  
          int startIndex = needsFirst ? 0 : 1;
          int count = desc.fCount + needsLast;
  
          bool uniformStops = true;
          const SkScalar uniformStep = desc.fPos[startIndex] - prev;
          for (int i = startIndex; i < count; i++) {
              // Pin the last value to 1.0, and make sure pos is monotonic.
              auto curr = (i == desc.fCount) ? 1 : SkTPin(desc.fPos[i], prev, 1.0f);
              uniformStops &= SkScalarNearlyEqual(uniformStep, curr - prev);
  
              *origPosPtr++ = prev = curr;
          }
  
          // If the stops are uniform, treat them as implicit.
          if (uniformStops) {
              fOrigPos = nullptr;
          }
      }
  }
  
  SkGradientShaderBase::~SkGradientShaderBase() {}
  
  void SkGradientShaderBase::flatten(SkWriteBuffer& buffer) const {
      Descriptor desc;
      desc.fColors = fOrigColors4f;
      desc.fColorSpace = fColorSpace;
      desc.fPos = fOrigPos;
      desc.fCount = fColorCount;
      desc.fTileMode = fTileMode;
      desc.fGradFlags = fGradFlags;
  
      const SkMatrix& m = this->getLocalMatrix();
      desc.fLocalMatrix = m.isIdentity() ? nullptr : &m;
      desc.flatten(buffer);
  }
  
  static void add_stop_color(SkRasterPipeline_GradientCtx* ctx, size_t stop, SkPMColor4f Fs, SkPMColor4f Bs) {
      (ctx->fs[0])[stop] = Fs.fR;
      (ctx->fs[1])[stop] = Fs.fG;
      (ctx->fs[2])[stop] = Fs.fB;
      (ctx->fs[3])[stop] = Fs.fA;
  
      (ctx->bs[0])[stop] = Bs.fR;
      (ctx->bs[1])[stop] = Bs.fG;
      (ctx->bs[2])[stop] = Bs.fB;
      (ctx->bs[3])[stop] = Bs.fA;
  }
  
  static void add_const_color(SkRasterPipeline_GradientCtx* ctx, size_t stop, SkPMColor4f color) {
      add_stop_color(ctx, stop, { 0, 0, 0, 0 }, color);
  }
  
  // Calculate a factor F and a bias B so that color = F*t + B when t is in range of
  // the stop. Assume that the distance between stops is 1/gapCount.
  static void init_stop_evenly(
      SkRasterPipeline_GradientCtx* ctx, float gapCount, size_t stop, SkPMColor4f c_l, SkPMColor4f c_r) {
      // Clankium's GCC 4.9 targeting ARMv7 is barfing when we use Sk4f math here, so go scalar...
      SkPMColor4f Fs = {
          (c_r.fR - c_l.fR) * gapCount,
          (c_r.fG - c_l.fG) * gapCount,
          (c_r.fB - c_l.fB) * gapCount,
          (c_r.fA - c_l.fA) * gapCount,
      };
      SkPMColor4f Bs = {
          c_l.fR - Fs.fR*(stop/gapCount),
          c_l.fG - Fs.fG*(stop/gapCount),
          c_l.fB - Fs.fB*(stop/gapCount),
          c_l.fA - Fs.fA*(stop/gapCount),
      };
      add_stop_color(ctx, stop, Fs, Bs);
  }
  
  // For each stop we calculate a bias B and a scale factor F, such that
  // for any t between stops n and n+1, the color we want is B[n] + F[n]*t.
  static void init_stop_pos(
      SkRasterPipeline_GradientCtx* ctx, size_t stop, float t_l, float t_r, SkPMColor4f c_l, SkPMColor4f c_r) {
      // See note about Clankium's old compiler in init_stop_evenly().
      SkPMColor4f Fs = {
          (c_r.fR - c_l.fR) / (t_r - t_l),
          (c_r.fG - c_l.fG) / (t_r - t_l),
          (c_r.fB - c_l.fB) / (t_r - t_l),
          (c_r.fA - c_l.fA) / (t_r - t_l),
      };
      SkPMColor4f Bs = {
          c_l.fR - Fs.fR*t_l,
          c_l.fG - Fs.fG*t_l,
          c_l.fB - Fs.fB*t_l,
          c_l.fA - Fs.fA*t_l,
      };
      ctx->ts[stop] = t_l;
      add_stop_color(ctx, stop, Fs, Bs);
  }
  
  bool SkGradientShaderBase::onAppendStages(const SkStageRec& rec) const {
      SkRasterPipeline* p = rec.fPipeline;
      SkArenaAlloc* alloc = rec.fAlloc;
      SkRasterPipeline_DecalTileCtx* decal_ctx = nullptr;
  
      SkMatrix matrix;
      if (!this->computeTotalInverse(rec.fMatrixProvider.localToDevice(), rec.fLocalM, &matrix)) {
          return false;
      }
      matrix.postConcat(fPtsToUnit);
  
      SkRasterPipeline_<256> postPipeline;
  
      p->append(SkRasterPipeline::seed_shader);
      p->append_matrix(alloc, matrix);
      this->appendGradientStages(alloc, p, &postPipeline);
  
      switch(fTileMode) {
          case SkTileMode::kMirror: p->append(SkRasterPipeline::mirror_x_1); break;
          case SkTileMode::kRepeat: p->append(SkRasterPipeline::repeat_x_1); break;
          case SkTileMode::kDecal:
              decal_ctx = alloc->make<SkRasterPipeline_DecalTileCtx>();
              decal_ctx->limit_x = SkBits2Float(SkFloat2Bits(1.0f) + 1);
              // reuse mask + limit_x stage, or create a custom decal_1 that just stores the mask
              p->append(SkRasterPipeline::decal_x, decal_ctx);
              [[fallthrough]];
  
          case SkTileMode::kClamp:
              if (!fOrigPos) {
                  // We clamp only when the stops are evenly spaced.
                  // If not, there may be hard stops, and clamping ruins hard stops at 0 and/or 1.
                  // In that case, we must make sure we're using the general "gradient" stage,
                  // which is the only stage that will correctly handle unclamped t.
                  p->append(SkRasterPipeline::clamp_x_1);
              }
              break;
      }
  
      const bool premulGrad = fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag;
  
      // Transform all of the colors to destination color space
      SkColor4fXformer xformedColors(fOrigColors4f, fColorCount, fColorSpace.get(), rec.fDstCS);
  
      auto prepareColor = [premulGrad, &xformedColors](int i) {
          SkColor4f c = xformedColors.fColors[i];
          return premulGrad ? c.premul()
                            : SkPMColor4f{ c.fR, c.fG, c.fB, c.fA };
      };
  
      // The two-stop case with stops at 0 and 1.
      if (fColorCount == 2 && fOrigPos == nullptr) {
          const SkPMColor4f c_l = prepareColor(0),
                            c_r = prepareColor(1);
  
          // See F and B below.
          auto ctx = alloc->make<SkRasterPipeline_EvenlySpaced2StopGradientCtx>();
          (Sk4f::Load(c_r.vec()) - Sk4f::Load(c_l.vec())).store(ctx->f);
          (                        Sk4f::Load(c_l.vec())).store(ctx->b);
          ctx->interpolatedInPremul = premulGrad;
  
          p->append(SkRasterPipeline::evenly_spaced_2_stop_gradient, ctx);
      } else {
          auto* ctx = alloc->make<SkRasterPipeline_GradientCtx>();
          ctx->interpolatedInPremul = premulGrad;
  
          // Note: In order to handle clamps in search, the search assumes a stop conceptully placed
          // at -inf. Therefore, the max number of stops is fColorCount+1.
          for (int i = 0; i < 4; i++) {
              // Allocate at least at for the AVX2 gather from a YMM register.
              ctx->fs[i] = alloc->makeArray<float>(std::max(fColorCount+1, 8));
              ctx->bs[i] = alloc->makeArray<float>(std::max(fColorCount+1, 8));
          }
  
          if (fOrigPos == nullptr) {
              // Handle evenly distributed stops.
  
              size_t stopCount = fColorCount;
              float gapCount = stopCount - 1;
  
              SkPMColor4f c_l = prepareColor(0);
              for (size_t i = 0; i < stopCount - 1; i++) {
                  SkPMColor4f c_r = prepareColor(i + 1);
                  init_stop_evenly(ctx, gapCount, i, c_l, c_r);
                  c_l = c_r;
              }
              add_const_color(ctx, stopCount - 1, c_l);
  
              ctx->stopCount = stopCount;
              p->append(SkRasterPipeline::evenly_spaced_gradient, ctx);
          } else {
              // Handle arbitrary stops.
  
              ctx->ts = alloc->makeArray<float>(fColorCount+1);
  
              // Remove the default stops inserted by SkGradientShaderBase::SkGradientShaderBase
              // because they are naturally handled by the search method.
              int firstStop;
              int lastStop;
              if (fColorCount > 2) {
                  firstStop = fOrigColors4f[0] != fOrigColors4f[1] ? 0 : 1;
                  lastStop = fOrigColors4f[fColorCount - 2] != fOrigColors4f[fColorCount - 1]
                             ? fColorCount - 1 : fColorCount - 2;
              } else {
                  firstStop = 0;
                  lastStop = 1;
              }
  
              size_t stopCount = 0;
              float  t_l = fOrigPos[firstStop];
              SkPMColor4f c_l = prepareColor(firstStop);
              add_const_color(ctx, stopCount++, c_l);
              // N.B. lastStop is the index of the last stop, not one after.
              for (int i = firstStop; i < lastStop; i++) {
                  float  t_r = fOrigPos[i + 1];
                  SkPMColor4f c_r = prepareColor(i + 1);
                  SkASSERT(t_l <= t_r);
                  if (t_l < t_r) {
                      init_stop_pos(ctx, stopCount, t_l, t_r, c_l, c_r);
                      stopCount += 1;
                  }
                  t_l = t_r;
                  c_l = c_r;
              }
  
              ctx->ts[stopCount] = t_l;
              add_const_color(ctx, stopCount++, c_l);
  
              ctx->stopCount = stopCount;
              p->append(SkRasterPipeline::gradient, ctx);
          }
      }
  
      if (decal_ctx) {
          p->append(SkRasterPipeline::check_decal_mask, decal_ctx);
      }
  
      if (!premulGrad && !this->colorsAreOpaque()) {
          p->append(SkRasterPipeline::premul);
      }
  
      p->extend(postPipeline);
  
      return true;
  }
  
  skvm::Color SkGradientShaderBase::onProgram(skvm::Builder* p,
                                              skvm::Coord device, skvm::Coord local,
                                              skvm::Color /*paint*/,
                                              const SkMatrixProvider& mats, const SkMatrix* localM,
                                              const SkColorInfo& dstInfo,
                                              skvm::Uniforms* uniforms, SkArenaAlloc* alloc) const {
      SkMatrix inv;
      if (!this->computeTotalInverse(mats.localToDevice(), localM, &inv)) {
          return {};
      }
      inv.postConcat(fPtsToUnit);
      inv.normalizePerspective();
  
      local = SkShaderBase::ApplyMatrix(p, inv, local, uniforms);
  
      skvm::I32 mask = p->splat(~0);
      skvm::F32 t = this->transformT(p,uniforms, local, &mask);
  
      // Perhaps unexpectedly, clamping is handled naturally by our search, so we
      // don't explicitly clamp t to [0,1].  That clamp would break hard stops
      // right at 0 or 1 boundaries in kClamp mode.  (kRepeat and kMirror always
      // produce values in [0,1].)
      switch(fTileMode) {
          case SkTileMode::kClamp:
              break;
  
          case SkTileMode::kDecal:
              mask &= (t == clamp01(t));
              break;
  
          case SkTileMode::kRepeat:
              t = fract(t);
              break;
  
          case SkTileMode::kMirror: {
              // t = | (t-1) - 2*(floor( (t-1)*0.5 )) - 1 |
              //       {-A-}      {--------B-------}
              skvm::F32 A = t - 1.0f,
                        B = floor(A * 0.5f);
              t = abs(A - (B + B) - 1.0f);
          } break;
      }
  
      // Transform our colors as we want them interpolated, in dst color space, possibly premul.
      SkImageInfo common = SkImageInfo::Make(fColorCount,1, kRGBA_F32_SkColorType
                                                          , kUnpremul_SkAlphaType),
                  src    = common.makeColorSpace(fColorSpace),
                  dst    = common.makeColorSpace(dstInfo.refColorSpace());
      if (fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag) {
          dst = dst.makeAlphaType(kPremul_SkAlphaType);
      }
  
      std::vector<float> rgba(4*fColorCount);  // TODO: SkSTArray?
      SkAssertResult(SkConvertPixels(dst,   rgba.data(), dst.minRowBytes(),
                                     src, fOrigColors4f, src.minRowBytes()));
  
      // Transform our colors into a scale factor f and bias b such that for
      // any t between stops i and i+1, the color we want is mad(t, f[i], b[i]).
      using F4 = skvx::Vec<4,float>;
      struct FB { F4 f,b; };
      skvm::Color color;
  
      auto uniformF = [&](float x) { return p->uniformF(uniforms->pushF(x)); };
  
      if (fColorCount == 2) {
          // 2-stop gradients have colors at 0 and 1, and so must be evenly spaced.
          SkASSERT(fOrigPos == nullptr);
  
          // With 2 stops, we upload the single FB as uniforms and interpolate directly with t.
          F4 lo = F4::Load(rgba.data() + 0),
             hi = F4::Load(rgba.data() + 4);
          F4 F = hi - lo,
             B = lo;
  
          auto T = clamp01(t);
          color = {
              T * uniformF(F[0]) + uniformF(B[0]),
              T * uniformF(F[1]) + uniformF(B[1]),
              T * uniformF(F[2]) + uniformF(B[2]),
              T * uniformF(F[3]) + uniformF(B[3]),
          };
      } else {
          // To handle clamps in search we add a conceptual stop at t=-inf, so we
          // may need up to fColorCount+1 FBs and fColorCount t stops between them:
          //
          //   FBs:         [color 0]  [color 0->1]  [color 1->2]  [color 2->3]  ...
          //   stops:  (-inf)        t0            t1            t2  ...
          //
          // Both these arrays could end up shorter if any hard stops share the same t.
          FB* fb = alloc->makeArrayDefault<FB>(fColorCount+1);
          std::vector<float> stops;  // TODO: SkSTArray?
          stops.reserve(fColorCount);
  
          // Here's our conceptual stop at t=-inf covering all t<=0, clamping to our first color.
          float  t_lo = this->getPos(0);
          F4 color_lo = F4::Load(rgba.data());
          fb[0] = { 0.0f, color_lo };
          // N.B. No stops[] entry for this implicit -inf.
  
          // Now the non-edge cases, calculating scale and bias between adjacent normal stops.
          for (int i = 1; i < fColorCount; i++) {
              float  t_hi = this->getPos(i);
              F4 color_hi = F4::Load(rgba.data() + 4*i);
  
              // If t_lo == t_hi, we're on a hard stop, and transition immediately to the next color.
              SkASSERT(t_lo <= t_hi);
              if (t_lo < t_hi) {
                  F4 f = (color_hi - color_lo) / (t_hi - t_lo),
                     b = color_lo - f*t_lo;
                  stops.push_back(t_lo);
                  fb[stops.size()] = {f,b};
              }
  
              t_lo = t_hi;
              color_lo = color_hi;
          }
          // Anything >= our final t clamps to our final color.
          stops.push_back(t_lo);
          fb[stops.size()] = { 0.0f, color_lo };
  
          // We'll gather FBs from that array we just created.
          skvm::Uniform fbs = uniforms->pushPtr(fb);
  
          // Find the two stops we need to interpolate.
          skvm::I32 ix;
          if (fOrigPos == nullptr) {
              // Evenly spaced stops... we can calculate ix directly.
              // Of note: we need to clamp t and skip over that conceptual -inf stop we made up.
              ix = trunc(clamp01(t) * uniformF(stops.size() - 1) + 1.0f);
          } else {
              // Starting ix at 0 bakes in our conceptual first stop at -inf.
              // TODO: good place to experiment with a loop in skvm.... stops.size() can be huge.
              ix = p->splat(0);
              for (float stop : stops) {
                  // ix += (t >= stop) ? +1 : 0 ~~>
                  // ix -= (t >= stop) ? -1 : 0
                  ix -= (t >= uniformF(stop));
              }
              // TODO: we could skip any of the default stops GradientShaderBase's ctor added
              // to ensure the full [0,1] span is covered.  This linear search doesn't need
              // them for correctness, and it'd be up to two fewer stops to check.
              // N.B. we do still need those stops for the fOrigPos == nullptr direct math path.
          }
  
          // A scale factor and bias for each lane, 8 total.
          // TODO: simpler, faster, tidier to push 8 uniform pointers, one for each struct lane?
          ix = shl(ix, 3);
          skvm::F32 Fr = gatherF(fbs, ix + 0);
          skvm::F32 Fg = gatherF(fbs, ix + 1);
          skvm::F32 Fb = gatherF(fbs, ix + 2);
          skvm::F32 Fa = gatherF(fbs, ix + 3);
  
          skvm::F32 Br = gatherF(fbs, ix + 4);
          skvm::F32 Bg = gatherF(fbs, ix + 5);
          skvm::F32 Bb = gatherF(fbs, ix + 6);
          skvm::F32 Ba = gatherF(fbs, ix + 7);
  
          // This is what we've been building towards!
          color = {
              t * Fr + Br,
              t * Fg + Bg,
              t * Fb + Bb,
              t * Fa + Ba,
          };
      }
  
      // If we interpolated unpremul, premul now to match our output convention.
      if (0 == (fGradFlags & SkGradientShader::kInterpolateColorsInPremul_Flag)
              && !fColorsAreOpaque) {
          color = premul(color);
      }
  
      return {
          pun_to_F32(mask & pun_to_I32(color.r)),
          pun_to_F32(mask & pun_to_I32(color.g)),
          pun_to_F32(mask & pun_to_I32(color.b)),
          pun_to_F32(mask & pun_to_I32(color.a)),
      };
  }
  
  
  bool SkGradientShaderBase::isOpaque() const {
      return fColorsAreOpaque && (this->getTileMode() != SkTileMode::kDecal);
  }
  
  static unsigned rounded_divide(unsigned numer, unsigned denom) {
      return (numer + (denom >> 1)) / denom;
  }
  
  bool SkGradientShaderBase::onAsLuminanceColor(SkColor* lum) const {
      // we just compute an average color.
      // possibly we could weight this based on the proportional width for each color
      //   assuming they are not evenly distributed in the fPos array.
      int r = 0;
      int g = 0;
      int b = 0;
      const int n = fColorCount;
      // TODO: use linear colors?
      for (int i = 0; i < n; ++i) {
          SkColor c = this->getLegacyColor(i);
          r += SkColorGetR(c);
          g += SkColorGetG(c);
          b += SkColorGetB(c);
      }
      *lum = SkColorSetRGB(rounded_divide(r, n), rounded_divide(g, n), rounded_divide(b, n));
      return true;
  }
  
  SkColor4fXformer::SkColor4fXformer(const SkColor4f* colors, int colorCount,
                                     SkColorSpace* src, SkColorSpace* dst) {
      fColors = colors;
  
      if (dst && !SkColorSpace::Equals(src, dst)) {
          fStorage.reset(colorCount);
  
          auto info = SkImageInfo::Make(colorCount,1, kRGBA_F32_SkColorType, kUnpremul_SkAlphaType);
  
          auto dstInfo = info.makeColorSpace(sk_ref_sp(dst));
          auto srcInfo = info.makeColorSpace(sk_ref_sp(src));
          SkAssertResult(SkConvertPixels(dstInfo, fStorage.begin(), info.minRowBytes(),
                                         srcInfo, fColors         , info.minRowBytes()));
  
          fColors = fStorage.begin();
      }
  }
  
  void SkGradientShaderBase::commonAsAGradient(GradientInfo* info) const {
      if (info) {
          if (info->fColorCount >= fColorCount) {
              if (info->fColors) {
                  for (int i = 0; i < fColorCount; ++i) {
                      info->fColors[i] = this->getLegacyColor(i);
                  }
              }
              if (info->fColorOffsets) {
                  for (int i = 0; i < fColorCount; ++i) {
                      info->fColorOffsets[i] = this->getPos(i);
                  }
              }
          }
          info->fColorCount = fColorCount;
          info->fTileMode = fTileMode;
          info->fGradientFlags = fGradFlags;
      }
  }
  
  ///////////////////////////////////////////////////////////////////////////////
  ///////////////////////////////////////////////////////////////////////////////
  
  // Return true if these parameters are valid/legal/safe to construct a gradient
  //
  static bool valid_grad(const SkColor4f colors[], const SkScalar pos[], int count,
                         SkTileMode tileMode) {
      return nullptr != colors && count >= 1 && (unsigned)tileMode < kSkTileModeCount;
  }
  
  static void desc_init(SkGradientShaderBase::Descriptor* desc,
                        const SkColor4f colors[], sk_sp<SkColorSpace> colorSpace,
                        const SkScalar pos[], int colorCount,
                        SkTileMode mode, uint32_t flags, const SkMatrix* localMatrix) {
      SkASSERT(colorCount > 1);
  
      desc->fColors       = colors;
      desc->fColorSpace   = std::move(colorSpace);
      desc->fPos          = pos;
      desc->fCount        = colorCount;
      desc->fTileMode     = mode;
      desc->fGradFlags    = flags;
      desc->fLocalMatrix  = localMatrix;
  }
  
  static SkColor4f average_gradient_color(const SkColor4f colors[], const SkScalar pos[],
                                          int colorCount) {
      // The gradient is a piecewise linear interpolation between colors. For a given interval,
      // the integral between the two endpoints is 0.5 * (ci + cj) * (pj - pi), which provides that
      // intervals average color. The overall average color is thus the sum of each piece. The thing
      // to keep in mind is that the provided gradient definition may implicitly use p=0 and p=1.
      Sk4f blend(0.0f);
      for (int i = 0; i < colorCount - 1; ++i) {
          // Calculate the average color for the interval between pos(i) and pos(i+1)
          Sk4f c0 = Sk4f::Load(&colors[i]);
          Sk4f c1 = Sk4f::Load(&colors[i + 1]);
  
          // when pos == null, there are colorCount uniformly distributed stops, going from 0 to 1,
          // so pos[i + 1] - pos[i] = 1/(colorCount-1)
          SkScalar w;
          if (pos) {
              // Match position fixing in SkGradientShader's constructor, clamping positions outside
              // [0, 1] and forcing the sequence to be monotonic
              SkScalar p0 = SkTPin(pos[i], 0.f, 1.f);
              SkScalar p1 = SkTPin(pos[i + 1], p0, 1.f);
              w = p1 - p0;
  
              // And account for any implicit intervals at the start or end of the positions
              if (i == 0) {
                  if (p0 > 0.0f) {
                      // The first color is fixed between p = 0 to pos[0], so 0.5*(ci + cj)*(pj - pi)
                      // becomes 0.5*(c + c)*(pj - 0) = c * pj
                      Sk4f c = Sk4f::Load(&colors[0]);
                      blend += p0 * c;
                  }
              }
              if (i == colorCount - 2) {
                  if (p1 < 1.f) {
                      // The last color is fixed between pos[n-1] to p = 1, so 0.5*(ci + cj)*(pj - pi)
                      // becomes 0.5*(c + c)*(1 - pi) = c * (1 - pi)
                      Sk4f c = Sk4f::Load(&colors[colorCount - 1]);
                      blend += (1.f - p1) * c;
                  }
              }
          } else {
              w = 1.f / (colorCount - 1);
          }
  
          blend += 0.5f * w * (c1 + c0);
      }
  
      SkColor4f avg;
      blend.store(&avg);
      return avg;
  }
  
  // The default SkScalarNearlyZero threshold of .0024 is too big and causes regressions for svg
  // gradients defined in the wild.
  static constexpr SkScalar kDegenerateThreshold = SK_Scalar1 / (1 << 15);
  
  // Except for special circumstances of clamped gradients, every gradient shape--when degenerate--
  // can be mapped to the same fallbacks. The specific shape factories must account for special
  // clamped conditions separately, this will always return the last color for clamped gradients.
  static sk_sp<SkShader> make_degenerate_gradient(const SkColor4f colors[], const SkScalar pos[],
                                                  int colorCount, sk_sp<SkColorSpace> colorSpace,
                                                  SkTileMode mode) {
      switch(mode) {
          case SkTileMode::kDecal:
              // normally this would reject the area outside of the interpolation region, so since
              // inside region is empty when the radii are equal, the entire draw region is empty
              return SkShaders::Empty();
          case SkTileMode::kRepeat:
          case SkTileMode::kMirror:
              // repeat and mirror are treated the same: the border colors are never visible,
              // but approximate the final color as infinite repetitions of the colors, so
              // it can be represented as the average color of the gradient.
              return SkShaders::Color(
                      average_gradient_color(colors, pos, colorCount), std::move(colorSpace));
          case SkTileMode::kClamp:
              // Depending on how the gradient shape degenerates, there may be a more specialized
              // fallback representation for the factories to use, but this is a reasonable default.
              return SkShaders::Color(colors[colorCount - 1], std::move(colorSpace));
      }
      SkDEBUGFAIL("Should not be reached");
      return nullptr;
  }
  
  // assumes colors is SkColor4f* and pos is SkScalar*
  #define EXPAND_1_COLOR(count)                \
       SkColor4f tmp[2];                       \
       do {                                    \
           if (1 == count) {                   \
               tmp[0] = tmp[1] = colors[0];    \
               colors = tmp;                   \
               pos = nullptr;                  \
               count = 2;                      \
           }                                   \
       } while (0)
  
  struct ColorStopOptimizer {
      ColorStopOptimizer(const SkColor4f* colors, const SkScalar* pos, int count, SkTileMode mode)
          : fColors(colors)
          , fPos(pos)
          , fCount(count) {
  
              if (!pos || count != 3) {
                  return;
              }
  
              if (SkScalarNearlyEqual(pos[0], 0.0f) &&
                  SkScalarNearlyEqual(pos[1], 0.0f) &&
                  SkScalarNearlyEqual(pos[2], 1.0f)) {
  
                  if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
                      colors[0] == colors[1]) {
  
                      // Ignore the leftmost color/pos.
                      fColors += 1;
                      fPos    += 1;
                      fCount   = 2;
                  }
              } else if (SkScalarNearlyEqual(pos[0], 0.0f) &&
                         SkScalarNearlyEqual(pos[1], 1.0f) &&
                         SkScalarNearlyEqual(pos[2], 1.0f)) {
  
                  if (SkTileMode::kRepeat == mode || SkTileMode::kMirror == mode ||
                      colors[1] == colors[2]) {
  
                      // Ignore the rightmost color/pos.
                      fCount  = 2;
                  }
              }
      }
  
      const SkColor4f* fColors;
      const SkScalar*  fPos;
      int              fCount;
  };
  
  struct ColorConverter {
      ColorConverter(const SkColor* colors, int count) {
          const float ONE_OVER_255 = 1.f / 255;
          for (int i = 0; i < count; ++i) {
              fColors4f.push_back({
                  SkColorGetR(colors[i]) * ONE_OVER_255,
                  SkColorGetG(colors[i]) * ONE_OVER_255,
                  SkColorGetB(colors[i]) * ONE_OVER_255,
                  SkColorGetA(colors[i]) * ONE_OVER_255 });
          }
      }
  
      SkSTArray<2, SkColor4f, true> fColors4f;
  };
  
  sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2],
                                               const SkColor colors[],
                                               const SkScalar pos[], int colorCount,
                                               SkTileMode mode,
                                               uint32_t flags,
                                               const SkMatrix* localMatrix) {
      ColorConverter converter(colors, colorCount);
      return MakeLinear(pts, converter.fColors4f.begin(), nullptr, pos, colorCount, mode, flags,
                        localMatrix);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2],
                                               const SkColor4f colors[],
                                               sk_sp<SkColorSpace> colorSpace,
                                               const SkScalar pos[], int count, SkTileMode mode) {
      return MakeLinear(pts, colors, std::move(colorSpace), pos, count, mode, 0, nullptr);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeLinear(const SkPoint pts[2],
                                               const SkColor4f colors[],
                                               sk_sp<SkColorSpace> colorSpace,
                                               const SkScalar pos[], int colorCount,
                                               SkTileMode mode,
                                               uint32_t flags,
                                               const SkMatrix* localMatrix) {
      if (!pts || !SkScalarIsFinite((pts[1] - pts[0]).length())) {
          return nullptr;
      }
      if (!valid_grad(colors, pos, colorCount, mode)) {
          return nullptr;
      }
      if (1 == colorCount) {
          return SkShaders::Color(colors[0], std::move(colorSpace));
      }
      if (localMatrix && !localMatrix->invert(nullptr)) {
          return nullptr;
      }
  
      if (SkScalarNearlyZero((pts[1] - pts[0]).length(), kDegenerateThreshold)) {
          // Degenerate gradient, the only tricky complication is when in clamp mode, the limit of
          // the gradient approaches two half planes of solid color (first and last). However, they
          // are divided by the line perpendicular to the start and end point, which becomes undefined
          // once start and end are exactly the same, so just use the end color for a stable solution.
          return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode);
      }
  
      ColorStopOptimizer opt(colors, pos, colorCount, mode);
  
      SkGradientShaderBase::Descriptor desc;
      desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
                localMatrix);
      return sk_make_sp<SkLinearGradient>(pts, desc);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius,
                                               const SkColor colors[],
                                               const SkScalar pos[], int colorCount,
                                               SkTileMode mode,
                                               uint32_t flags,
                                               const SkMatrix* localMatrix) {
      ColorConverter converter(colors, colorCount);
      return MakeRadial(center, radius, converter.fColors4f.begin(), nullptr, pos, colorCount, mode,
                        flags, localMatrix);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius,
                                               const SkColor4f colors[],
                                               sk_sp<SkColorSpace> colorSpace,
                                               const SkScalar pos[], int count, SkTileMode mode) {
      return MakeRadial(center, radius, colors, std::move(colorSpace), pos, count, mode, 0, nullptr);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeRadial(const SkPoint& center, SkScalar radius,
                                               const SkColor4f colors[],
                                               sk_sp<SkColorSpace> colorSpace,
                                               const SkScalar pos[], int colorCount,
                                               SkTileMode mode,
                                               uint32_t flags,
                                               const SkMatrix* localMatrix) {
      if (radius < 0) {
          return nullptr;
      }
      if (!valid_grad(colors, pos, colorCount, mode)) {
          return nullptr;
      }
      if (1 == colorCount) {
          return SkShaders::Color(colors[0], std::move(colorSpace));
      }
      if (localMatrix && !localMatrix->invert(nullptr)) {
          return nullptr;
      }
  
      if (SkScalarNearlyZero(radius, kDegenerateThreshold)) {
          // Degenerate gradient optimization, and no special logic needed for clamped radial gradient
          return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode);
      }
  
      ColorStopOptimizer opt(colors, pos, colorCount, mode);
  
      SkGradientShaderBase::Descriptor desc;
      desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
                localMatrix);
      return sk_make_sp<SkRadialGradient>(center, radius, desc);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
                                                        SkScalar startRadius,
                                                        const SkPoint& end,
                                                        SkScalar endRadius,
                                                        const SkColor colors[],
                                                        const SkScalar pos[],
                                                        int colorCount,
                                                        SkTileMode mode,
                                                        uint32_t flags,
                                                        const SkMatrix* localMatrix) {
      ColorConverter converter(colors, colorCount);
      return MakeTwoPointConical(start, startRadius, end, endRadius, converter.fColors4f.begin(),
                                 nullptr, pos, colorCount, mode, flags, localMatrix);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
                                                        SkScalar startRadius,
                                                        const SkPoint& end,
                                                        SkScalar endRadius,
                                                        const SkColor4f colors[],
                                                        sk_sp<SkColorSpace> colorSpace,
                                                        const SkScalar pos[],
                                                        int count, SkTileMode mode) {
      return MakeTwoPointConical(start, startRadius, end, endRadius, colors,
                                 std::move(colorSpace), pos, count, mode, 0, nullptr);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeTwoPointConical(const SkPoint& start,
                                                        SkScalar startRadius,
                                                        const SkPoint& end,
                                                        SkScalar endRadius,
                                                        const SkColor4f colors[],
                                                        sk_sp<SkColorSpace> colorSpace,
                                                        const SkScalar pos[],
                                                        int colorCount,
                                                        SkTileMode mode,
                                                        uint32_t flags,
                                                        const SkMatrix* localMatrix) {
      if (startRadius < 0 || endRadius < 0) {
          return nullptr;
      }
      if (!valid_grad(colors, pos, colorCount, mode)) {
          return nullptr;
      }
      if (SkScalarNearlyZero((start - end).length(), kDegenerateThreshold)) {
          // If the center positions are the same, then the gradient is the radial variant of a 2 pt
          // conical gradient, an actual radial gradient (startRadius == 0), or it is fully degenerate
          // (startRadius == endRadius).
          if (SkScalarNearlyEqual(startRadius, endRadius, kDegenerateThreshold)) {
              // Degenerate case, where the interpolation region area approaches zero. The proper
              // behavior depends on the tile mode, which is consistent with the default degenerate
              // gradient behavior, except when mode = clamp and the radii > 0.
              if (mode == SkTileMode::kClamp && endRadius > kDegenerateThreshold) {
                  // The interpolation region becomes an infinitely thin ring at the radius, so the
                  // final gradient will be the first color repeated from p=0 to 1, and then a hard
                  // stop switching to the last color at p=1.
                  static constexpr SkScalar circlePos[3] = {0, 1, 1};
                  SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
                  return MakeRadial(start, endRadius, reColors, std::move(colorSpace),
                                    circlePos, 3, mode, flags, localMatrix);
              } else {
                  // Otherwise use the default degenerate case
                  return make_degenerate_gradient(
                          colors, pos, colorCount, std::move(colorSpace), mode);
              }
          } else if (SkScalarNearlyZero(startRadius, kDegenerateThreshold)) {
              // We can treat this gradient as radial, which is faster. If we got here, we know
              // that endRadius is not equal to 0, so this produces a meaningful gradient
              return MakeRadial(start, endRadius, colors, std::move(colorSpace), pos, colorCount,
                                mode, flags, localMatrix);
          }
          // Else it's the 2pt conical radial variant with no degenerate radii, so fall through to the
          // regular 2pt constructor.
      }
  
      if (localMatrix && !localMatrix->invert(nullptr)) {
          return nullptr;
      }
      EXPAND_1_COLOR(colorCount);
  
      ColorStopOptimizer opt(colors, pos, colorCount, mode);
  
      SkGradientShaderBase::Descriptor desc;
      desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
                localMatrix);
      return SkTwoPointConicalGradient::Create(start, startRadius, end, endRadius, desc);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
                                              const SkColor colors[],
                                              const SkScalar pos[],
                                              int colorCount,
                                              SkTileMode mode,
                                              SkScalar startAngle,
                                              SkScalar endAngle,
                                              uint32_t flags,
                                              const SkMatrix* localMatrix) {
      ColorConverter converter(colors, colorCount);
      return MakeSweep(cx, cy, converter.fColors4f.begin(), nullptr, pos, colorCount,
                       mode, startAngle, endAngle, flags, localMatrix);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
                                              const SkColor4f colors[],
                                              sk_sp<SkColorSpace> colorSpace,
                                              const SkScalar pos[], int count,
                                              uint32_t flags, const SkMatrix* localMatrix) {
      return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count,
                       SkTileMode::kClamp, 0, 360, flags, localMatrix);
  }
  sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
                                              const SkColor4f colors[],
                                              sk_sp<SkColorSpace> colorSpace,
                                              const SkScalar pos[], int count) {
      return MakeSweep(cx, cy, colors, std::move(colorSpace), pos, count, 0, nullptr);
  }
  
  sk_sp<SkShader> SkGradientShader::MakeSweep(SkScalar cx, SkScalar cy,
                                              const SkColor4f colors[],
                                              sk_sp<SkColorSpace> colorSpace,
                                              const SkScalar pos[],
                                              int colorCount,
                                              SkTileMode mode,
                                              SkScalar startAngle,
                                              SkScalar endAngle,
                                              uint32_t flags,
                                              const SkMatrix* localMatrix) {
      if (!valid_grad(colors, pos, colorCount, mode)) {
          return nullptr;
      }
      if (1 == colorCount) {
          return SkShaders::Color(colors[0], std::move(colorSpace));
      }
      if (!SkScalarIsFinite(startAngle) || !SkScalarIsFinite(endAngle) || startAngle > endAngle) {
          return nullptr;
      }
      if (localMatrix && !localMatrix->invert(nullptr)) {
          return nullptr;
      }
  
      if (SkScalarNearlyEqual(startAngle, endAngle, kDegenerateThreshold)) {
          // Degenerate gradient, which should follow default degenerate behavior unless it is
          // clamped and the angle is greater than 0.
          if (mode == SkTileMode::kClamp && endAngle > kDegenerateThreshold) {
              // In this case, the first color is repeated from 0 to the angle, then a hardstop
              // switches to the last color (all other colors are compressed to the infinitely thin
              // interpolation region).
              static constexpr SkScalar clampPos[3] = {0, 1, 1};
              SkColor4f reColors[3] = {colors[0], colors[0], colors[colorCount - 1]};
              return MakeSweep(cx, cy, reColors, std::move(colorSpace), clampPos, 3, mode, 0,
                               endAngle, flags, localMatrix);
          } else {
              return make_degenerate_gradient(colors, pos, colorCount, std::move(colorSpace), mode);
          }
      }
  
      if (startAngle <= 0 && endAngle >= 360) {
          // If the t-range includes [0,1], then we can always use clamping (presumably faster).
          mode = SkTileMode::kClamp;
      }
  
      ColorStopOptimizer opt(colors, pos, colorCount, mode);
  
      SkGradientShaderBase::Descriptor desc;
      desc_init(&desc, opt.fColors, std::move(colorSpace), opt.fPos, opt.fCount, mode, flags,
                localMatrix);
  
      const SkScalar t0 = startAngle / 360,
                     t1 =   endAngle / 360;
  
      return sk_make_sp<SkSweepGradient>(SkPoint::Make(cx, cy), t0, t1, desc);
  }
  
  void SkGradientShader::RegisterFlattenables() {
      SK_REGISTER_FLATTENABLE(SkLinearGradient);
      SK_REGISTER_FLATTENABLE(SkRadialGradient);
      SK_REGISTER_FLATTENABLE(SkSweepGradient);
      SK_REGISTER_FLATTENABLE(SkTwoPointConicalGradient);
  }
  