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 /**
22  * Base class for true Gradient shader delegate.
23  */
24 public abstract class Gradient_Delegate extends Shader_Delegate {
25 
26     protected final int[] mColors;
27     protected final float[] mPositions;
28 
29     @Override
isSupported()30     public boolean isSupported() {
31         // all gradient shaders are supported.
32         return true;
33     }
34 
35     @Override
getSupportMessage()36     public String getSupportMessage() {
37         // all gradient shaders are supported, no need for a gradient support
38         return null;
39     }
40 
41     /**
42      * Creates the base shader and do some basic test on the parameters.
43      *
44      * @param colors The colors to be distributed along the gradient line
45      * @param positions May be null. The relative positions [0..1] of each
46      *            corresponding color in the colors array. If this is null, the
47      *            the colors are distributed evenly along the gradient line.
48      */
Gradient_Delegate(int colors[], float positions[])49     protected Gradient_Delegate(int colors[], float positions[]) {
50         if (colors.length < 2) {
51             throw new IllegalArgumentException("needs >= 2 number of colors");
52         }
53         if (positions != null && colors.length != positions.length) {
54             throw new IllegalArgumentException("color and position arrays must be of equal length");
55         }
56 
57         if (positions == null) {
58             float spacing = 1.f / (colors.length - 1);
59             positions = new float[colors.length];
60             positions[0] = 0.f;
61             positions[colors.length-1] = 1.f;
62             for (int i = 1; i < colors.length - 1 ; i++) {
63                 positions[i] = spacing * i;
64             }
65         }
66 
67         mColors = colors;
68         mPositions = positions;
69     }
70 
71     /**
72      * Base class for (Java) Gradient Paints. This handles computing the gradient colors based
73      * on the color and position lists, as well as the {@link TileMode}
74      *
75      */
76     protected abstract static class GradientPaint implements java.awt.Paint {
77         private final static int GRADIENT_SIZE = 100;
78 
79         private final int[] mColors;
80         private final float[] mPositions;
81         private final TileMode mTileMode;
82         private int[] mGradient;
83 
GradientPaint(int[] colors, float[] positions, TileMode tileMode)84         protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
85             mColors = colors;
86             mPositions = positions;
87             mTileMode = tileMode;
88         }
89 
90         @Override
getTransparency()91         public int getTransparency() {
92             return java.awt.Paint.TRANSLUCENT;
93         }
94 
95         /**
96          * Pre-computes the colors for the gradient. This must be called once before any call
97          * to {@link #getGradientColor(float)}
98          */
precomputeGradientColors()99         protected void precomputeGradientColors() {
100             if (mGradient == null) {
101                 // actually create an array with an extra size, so that we can really go
102                 // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0
103                 mGradient = new int[GRADIENT_SIZE+1];
104 
105                 int prevPos = 0;
106                 int nextPos = 1;
107                 for (int i  = 0 ; i <= GRADIENT_SIZE ; i++) {
108                     // compute current position
109                     float currentPos = (float)i/GRADIENT_SIZE;
110                     while (currentPos > mPositions[nextPos]) {
111                         prevPos = nextPos++;
112                     }
113 
114                     float percent = (currentPos - mPositions[prevPos]) /
115                             (mPositions[nextPos] - mPositions[prevPos]);
116 
117                     mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
118                 }
119             }
120         }
121 
122         /**
123          * Returns the color based on the position in the gradient.
124          * <var>pos</var> can be anything, even &lt; 0 or &gt; > 1, as the gradient
125          * will use {@link TileMode} value to convert it into a [0,1] value.
126          */
getGradientColor(float pos)127         protected int getGradientColor(float pos) {
128             if (pos < 0.f) {
129                 if (mTileMode != null) {
130                     switch (mTileMode) {
131                         case CLAMP:
132                             pos = 0.f;
133                             break;
134                         case REPEAT:
135                             // remove the integer part to stay in the [0,1] range.
136                             // we also need to invert the value from [-1,0] to [0, 1]
137                             pos = pos - (float)Math.floor(pos);
138                             break;
139                         case MIRROR:
140                             // this is the same as the positive side, just make the value positive
141                             // first.
142                             pos = Math.abs(pos);
143 
144                             // get the integer and the decimal part
145                             int intPart = (int)Math.floor(pos);
146                             pos = pos - intPart;
147                             // 0 -> 1 : normal order
148                             // 1 -> 2: mirrored
149                             // etc..
150                             // this means if the intpart is odd we invert
151                             if ((intPart % 2) == 1) {
152                                 pos = 1.f - pos;
153                             }
154                             break;
155                     }
156                 } else {
157                     pos = 0.0f;
158                 }
159             } else if (pos > 1f) {
160                 if (mTileMode != null) {
161                     switch (mTileMode) {
162                         case CLAMP:
163                             pos = 1.f;
164                             break;
165                         case REPEAT:
166                             // remove the integer part to stay in the [0,1] range
167                             pos = pos - (float)Math.floor(pos);
168                             break;
169                         case MIRROR:
170                             // get the integer and the decimal part
171                             int intPart = (int)Math.floor(pos);
172                             pos = pos - intPart;
173                             // 0 -> 1 : normal order
174                             // 1 -> 2: mirrored
175                             // etc..
176                             // this means if the intpart is odd we invert
177                             if ((intPart % 2) == 1) {
178                                 pos = 1.f - pos;
179                             }
180                             break;
181                     }
182                 } else {
183                     pos = 1.0f;
184                 }
185             }
186 
187             int index = (int)((pos * GRADIENT_SIZE) + .5);
188 
189             return mGradient[index];
190         }
191 
192         /**
193          * Returns the color between c1, and c2, based on the percent of the distance
194          * between c1 and c2.
195          */
computeColor(int c1, int c2, float percent)196         private int computeColor(int c1, int c2, float percent) {
197             int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
198             int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
199             int g = computeChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
200             int b = computeChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
201             return a << 24 | r << 16 | g << 8 | b;
202         }
203 
204         /**
205          * Returns the channel value between 2 values based on the percent of the distance between
206          * the 2 values..
207          */
computeChannel(int c1, int c2, float percent)208         private int computeChannel(int c1, int c2, float percent) {
209             return c1 + (int)((percent * (c2-c1)) + .5);
210         }
211     }
212 }
213