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    @Override
30    public boolean isSupported() {
31        // all gradient shaders are supported.
32        return true;
33    }
34
35    @Override
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     */
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
84        protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
85            mColors = colors;
86            mPositions = positions;
87            mTileMode = tileMode;
88        }
89
90        @Override
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         */
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         */
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         */
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         */
208        private int computeChannel(int c1, int c2, float percent) {
209            return c1 + (int)((percent * (c2-c1)) + .5);
210        }
211    }
212}
213