1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.graphics;
18 
19 import android.graphics.Shader.TileMode;
20 
21 import java.util.Arrays;
22 
23 /**
24  * Base class for true Gradient shader delegate.
25  */
26 public abstract class Gradient_Delegate extends Shader_Delegate {
27 
28     protected final int[] mColors;
29     protected final float[] mPositions;
30 
31     @Override
isSupported()32     public boolean isSupported() {
33         // all gradient shaders are supported.
34         return true;
35     }
36 
37     @Override
getSupportMessage()38     public String getSupportMessage() {
39         // all gradient shaders are supported, no need for a gradient support
40         return null;
41     }
42 
43     /**
44      * Creates the base shader and do some basic test on the parameters.
45      *
46      * @param nativeMatrix reference to the shader's native transformation matrix
47      * @param colors The colors to be distributed along the gradient line
48      * @param positions May be null. The relative positions [0..1] of each
49      *            corresponding color in the colors array. If this is null, the
50      *            the colors are distributed evenly along the gradient line.
51      */
Gradient_Delegate(long nativeMatrix, long[] colors, float[] positions)52     protected Gradient_Delegate(long nativeMatrix, long[] colors, float[] positions) {
53         super(nativeMatrix);
54         assert colors.length >= 2 : "needs >= 2 number of colors";
55 
56         if (positions == null) {
57             float spacing = 1.f / (colors.length - 1);
58             positions = new float[colors.length];
59             positions[0] = 0.f;
60             positions[colors.length - 1] = 1.f;
61             for (int i = 1; i < colors.length - 1; i++) {
62                 positions[i] = spacing * i;
63             }
64         } else {
65             assert colors.length == positions.length :
66                     "color and position " + "arrays must be of equal length";
67             positions[0] = Math.min(Math.max(0, positions[0]), 1);
68             for (int i = 1; i < positions.length; i++) {
69                 positions[i] = Math.min(Math.max(positions[i-1], positions[i]), 1);
70             }
71         }
72 
73         mColors = Arrays.stream(colors).mapToInt(Color::toArgb).toArray();
74         mPositions = positions;
75     }
76 
77     /**
78      * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
79      * on the color and position lists, as well as the {@link TileMode}
80      *
81      */
82     protected abstract static class GradientPaint implements java.awt.Paint {
83         private final static int GRADIENT_SIZE = 100;
84 
85         private final int[] mColors;
86         private final float[] mPositions;
87         private final TileMode mTileMode;
88         private int[] mGradient;
89 
GradientPaint(int[] colors, float[] positions, TileMode tileMode)90         protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
91             mColors = colors;
92             mPositions = positions;
93             mTileMode = tileMode;
94         }
95 
96         @Override
getTransparency()97         public int getTransparency() {
98             return java.awt.Paint.TRANSLUCENT;
99         }
100 
101         /**
102          * Pre-computes the colors for the gradient. This must be called once before any call
103          * to {@link #getGradientColor(float)}
104          */
precomputeGradientColors()105         protected void precomputeGradientColors() {
106             if (mGradient == null) {
107                 // actually create an array with an extra size, so that we can really go
108                 // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
109                 mGradient = new int[GRADIENT_SIZE+1];
110 
111                 int prevPos = 0;
112                 int nextPos = 1;
113                 for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
114                     // compute current position
115                     float currentPos = (float)i/GRADIENT_SIZE;
116 
117                     if (currentPos < mPositions[0]) {
118                         mGradient[i] = mColors[0];
119                         continue;
120                     }
121 
122                     while (nextPos < mPositions.length && currentPos >= mPositions[nextPos]) {
123                         prevPos = nextPos++;
124                     }
125 
126                     if (nextPos == mPositions.length || currentPos == prevPos) {
127                         mGradient[i] = mColors[prevPos];
128                     } else {
129                         float percent = (currentPos - mPositions[prevPos]) /
130                                 (mPositions[nextPos] - mPositions[prevPos]);
131 
132                         mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
133                     }
134                 }
135             }
136         }
137 
138         /**
139          * Returns the color based on the position in the gradient.
140          * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
141          * will use {@link TileMode} value to convert it into a [0,1] value.
142          */
getGradientColor(float pos)143         protected int getGradientColor(float pos) {
144             if (pos < 0.f) {
145                 if (mTileMode != null) {
146                     switch (mTileMode) {
147                         case CLAMP:
148                             pos = 0.f;
149                             break;
150                         case REPEAT:
151                             // remove the integer part to stay in the [0,1] range.
152                             // we also need to invert the value from [-1,0] to [0, 1]
153                             pos = pos - (float)Math.floor(pos);
154                             break;
155                         case MIRROR:
156                             // this is the same as the positive side, just make the value positive
157                             // first.
158                             pos = Math.abs(pos);
159 
160                             // get the integer and the decimal part
161                             int intPart = (int)Math.floor(pos);
162                             pos = pos - intPart;
163                             // 0 -> 1 : normal order
164                             // 1 -> 2: mirrored
165                             // etc..
166                             // this means if the intpart is odd we invert
167                             if ((intPart % 2) == 1) {
168                                 pos = 1.f - pos;
169                             }
170                             break;
171                     }
172                 } else {
173                     pos = 0.0f;
174                 }
175             } else if (pos > 1f) {
176                 if (mTileMode != null) {
177                     switch (mTileMode) {
178                         case CLAMP:
179                             pos = 1.f;
180                             break;
181                         case REPEAT:
182                             // remove the integer part to stay in the [0,1] range
183                             pos = pos - (float)Math.floor(pos);
184                             break;
185                         case MIRROR:
186                             // get the integer and the decimal part
187                             int intPart = (int)Math.floor(pos);
188                             pos = pos - intPart;
189                             // 0 -> 1 : normal order
190                             // 1 -> 2: mirrored
191                             // etc..
192                             // this means if the intpart is odd we invert
193                             if ((intPart % 2) == 1) {
194                                 pos = 1.f - pos;
195                             }
196                             break;
197                     }
198                 } else {
199                     pos = 1.0f;
200                 }
201             }
202 
203             int index = (int)((pos * GRADIENT_SIZE) + .5);
204 
205             return mGradient[index];
206         }
207 
208         /**
209          * Returns the color between c1, and c2, based on the percent of the distance
210          * between c1 and c2.
211          */
computeColor(int c1, int c2, float percent)212         private int computeColor(int c1, int c2, float percent) {
213             int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
214             int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
215             int g = computeChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
216             int b = computeChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
217             return a << 24 | r << 16 | g << 8 | b;
218         }
219 
220         /**
221          * Returns the channel value between 2 values based on the percent of the distance between
222          * the 2 values..
223          */
computeChannel(int c1, int c2, float percent)224         private int computeChannel(int c1, int c2, float percent) {
225             return c1 + (int)((percent * (c2-c1)) + .5);
226         }
227     }
228 }
229