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