content: /*
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.graphics;
  
  import android.graphics.Shader.TileMode;
  
  import java.util.Arrays;
  
  /**
   * Base class for true Gradient shader delegate.
   */
  public abstract class Gradient_Delegate extends Shader_Delegate {
  
      protected final int[] mColors;
      protected final float[] mPositions;
  
      @Override
      public boolean isSupported() {
          // all gradient shaders are supported.
          return true;
      }
  
      @Override
      public String getSupportMessage() {
          // all gradient shaders are supported, no need for a gradient support
          return null;
      }
  
      /**
       * Creates the base shader and do some basic test on the parameters.
       *
       * @param nativeMatrix reference to the shader's native transformation matrix
       * @param colors The colors to be distributed along the gradient line
       * @param positions May be null. The relative positions [0..1] of each
       *            corresponding color in the colors array. If this is null, the
       *            the colors are distributed evenly along the gradient line.
       */
      protected Gradient_Delegate(long nativeMatrix, long[] colors, float[] positions) {
          super(nativeMatrix);
          assert colors.length >= 2 : "needs >= 2 number of colors";
  
          if (positions == null) {
              float spacing = 1.f / (colors.length - 1);
              positions = new float[colors.length];
              positions[0] = 0.f;
              positions[colors.length - 1] = 1.f;
              for (int i = 1; i < colors.length - 1; i++) {
                  positions[i] = spacing * i;
              }
          } else {
              assert colors.length == positions.length :
                      "color and position " + "arrays must be of equal length";
              positions[0] = Math.min(Math.max(0, positions[0]), 1);
              for (int i = 1; i < positions.length; i++) {
                  positions[i] = Math.min(Math.max(positions[i-1], positions[i]), 1);
              }
          }
  
          mColors = Arrays.stream(colors).mapToInt(Color::toArgb).toArray();
          mPositions = positions;
      }
  
      /**
       * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
       * on the color and position lists, as well as the {@link TileMode}
       *
       */
      protected abstract static class GradientPaint implements java.awt.Paint {
          private final static int GRADIENT_SIZE = 100;
  
          private final int[] mColors;
          private final float[] mPositions;
          private final TileMode mTileMode;
          private int[] mGradient;
  
          protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
              mColors = colors;
              mPositions = positions;
              mTileMode = tileMode;
          }
  
          @Override
          public int getTransparency() {
              return java.awt.Paint.TRANSLUCENT;
          }
  
          /**
           * Pre-computes the colors for the gradient. This must be called once before any call
           * to {@link #getGradientColor(float)}
           */
          protected void precomputeGradientColors() {
              if (mGradient == null) {
                  // actually create an array with an extra size, so that we can really go
                  // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
                  mGradient = new int[GRADIENT_SIZE+1];
  
                  int prevPos = 0;
                  int nextPos = 1;
                  for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
                      // compute current position
                      float currentPos = (float)i/GRADIENT_SIZE;
  
                      if (currentPos < mPositions[0]) {
                          mGradient[i] = mColors[0];
                          continue;
                      }
  
                      while (nextPos < mPositions.length && currentPos >= mPositions[nextPos]) {
                          prevPos = nextPos++;
                      }
  
                      if (nextPos == mPositions.length || currentPos == prevPos) {
                          mGradient[i] = mColors[prevPos];
                      } else {
                          float percent = (currentPos - mPositions[prevPos]) /
                                  (mPositions[nextPos] - mPositions[prevPos]);
  
                          mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
                      }
                  }
              }
          }
  
          /**
           * Returns the color based on the position in the gradient.
           * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
           * will use {@link TileMode} value to convert it into a [0,1] value.
           */
          protected int getGradientColor(float pos) {
              if (pos < 0.f) {
                  if (mTileMode != null) {
                      switch (mTileMode) {
                          case CLAMP:
                              pos = 0.f;
                              break;
                          case REPEAT:
                              // remove the integer part to stay in the [0,1] range.
                              // we also need to invert the value from [-1,0] to [0, 1]
                              pos = pos - (float)Math.floor(pos);
                              break;
                          case MIRROR:
                              // this is the same as the positive side, just make the value positive
                              // first.
                              pos = Math.abs(pos);
  
                              // get the integer and the decimal part
                              int intPart = (int)Math.floor(pos);
                              pos = pos - intPart;
                              // 0 -> 1 : normal order
                              // 1 -> 2: mirrored
                              // etc..
                              // this means if the intpart is odd we invert
                              if ((intPart % 2) == 1) {
                                  pos = 1.f - pos;
                              }
                              break;
                      }
                  } else {
                      pos = 0.0f;
                  }
              } else if (pos > 1f) {
                  if (mTileMode != null) {
                      switch (mTileMode) {
                          case CLAMP:
                              pos = 1.f;
                              break;
                          case REPEAT:
                              // remove the integer part to stay in the [0,1] range
                              pos = pos - (float)Math.floor(pos);
                              break;
                          case MIRROR:
                              // get the integer and the decimal part
                              int intPart = (int)Math.floor(pos);
                              pos = pos - intPart;
                              // 0 -> 1 : normal order
                              // 1 -> 2: mirrored
                              // etc..
                              // this means if the intpart is odd we invert
                              if ((intPart % 2) == 1) {
                                  pos = 1.f - pos;
                              }
                              break;
                      }
                  } else {
                      pos = 1.0f;
                  }
              }
  
              int index = (int)((pos * GRADIENT_SIZE) + .5);
  
              return mGradient[index];
          }
  
          /**
           * Returns the color between c1, and c2, based on the percent of the distance
           * between c1 and c2.
           */
          private int computeColor(int c1, int c2, float percent) {
              int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
              int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
              int g = computeChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
              int b = computeChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
              return a << 24 | r << 16 | g << 8 | b;
          }
  
          /**
           * Returns the channel value between 2 values based on the percent of the distance between
           * the 2 values..
           */
          private int computeChannel(int c1, int c2, float percent) {
              return c1 + (int)((percent * (c2-c1)) + .5);
          }
      }
  }
  