Gradient_Delegate.java revision 251d2e99245095369b52d891a660b2ed270f02e0
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                            // careful: this is a negative value, so use ceil instead of floor
124                            pos = pos - (float)Math.ceil(pos);
125                            break;
126                        case MIRROR:
127                            // get the integer and the decimal part
128                            // careful: this is a negative value, so use ceil instead of floor
129                            int intPart = (int)Math.ceil(pos);
130                            pos = pos - intPart;
131                            // 0  -> -1 : mirrored order
132                            // -1 -> -2: normal order
133                            // etc..
134                            // this means if the intpart is even we invert
135                            if ((intPart % 2) == 0) {
136                                pos = 1.f - pos;
137                            }
138                            break;
139                    }
140                } else {
141                    pos = 0.0f;
142                }
143            } else if (pos > 1f) {
144                if (mTileMode != null) {
145                    switch (mTileMode) {
146                        case CLAMP:
147                            pos = 1.f;
148                            break;
149                        case REPEAT:
150                            // remove the integer part to stay in the [0,1] range
151                            pos = pos - (float)Math.floor(pos);
152                            break;
153                        case MIRROR:
154                            // get the integer and the decimal part
155                            int intPart = (int)Math.floor(pos);
156                            pos = pos - intPart;
157                            // 0 -> 1 : normal order
158                            // 1 -> 2: mirrored
159                            // etc..
160                            // this means if the intpart is odd we invert
161                            if ((intPart % 2) == 1) {
162                                pos = 1.f - pos;
163                            }
164                            break;
165                    }
166                } else {
167                    pos = 1.0f;
168                }
169            }
170
171            int index = (int)((pos * GRADIENT_SIZE) + .5);
172
173            return mGradient[index];
174        }
175
176        /**
177         * Returns the color between c1, and c2, based on the percent of the distance
178         * between c1 and c2.
179         */
180        private int computeColor(int c1, int c2, float percent) {
181            int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
182            int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
183            int g = computeChannel((c1 >>  8) & 0xFF, (c2 >>  8) & 0xFF, percent);
184            int b = computeChannel((c1      ) & 0xFF, (c2      ) & 0xFF, percent);
185            return a << 24 | r << 16 | g << 8 | b;
186        }
187
188        /**
189         * Returns the channel value between 2 values based on the percent of the distance between
190         * the 2 values..
191         */
192        private int computeChannel(int c1, int c2, float percent) {
193            return c1 + (int)((percent * (c2-c1)) + .5);
194        }
195    }
196}
197