/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.graphics; import java.awt.Paint; import java.awt.PaintContext; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.Raster; public class LinearGradient extends Shader { private Paint mJavaPaint; /** * Create a shader that draws a linear gradient along a line. * * @param x0 The x-coordinate for the start of the gradient line * @param y0 The y-coordinate for the start of the gradient line * @param x1 The x-coordinate for the end of the gradient line * @param y1 The y-coordinate for the end of the gradient line * @param colors The colors to be distributed along the gradient line * @param positions May be null. The relative positions [0..1] of each * corresponding color in the colors array. If this is null, the * the colors are distributed evenly along the gradient line. * @param tile The Shader tiling mode */ public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], TileMode tile) { if (colors.length < 2) { throw new IllegalArgumentException("needs >= 2 number of colors"); } if (positions != null && colors.length != positions.length) { throw new IllegalArgumentException("color and position arrays must be of equal length"); } if (positions == null) { float spacing = 1.f / (colors.length - 1); positions = new float[colors.length]; positions[0] = 0.f; positions[colors.length-1] = 1.f; for (int i = 1; i < colors.length - 1 ; i++) { positions[i] = spacing * i; } } mJavaPaint = new MultiPointLinearGradientPaint(x0, y0, x1, y1, colors, positions, tile); } /** * Create a shader that draws a linear gradient along a line. * * @param x0 The x-coordinate for the start of the gradient line * @param y0 The y-coordinate for the start of the gradient line * @param x1 The x-coordinate for the end of the gradient line * @param y1 The y-coordinate for the end of the gradient line * @param color0 The color at the start of the gradient line. * @param color1 The color at the end of the gradient line. * @param tile The Shader tiling mode */ public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, TileMode tile) { this(x0, y0, x1, y1, new int[] { color0, color1}, null /*positions*/, tile); } // ---------- Custom Methods @Override public Paint getJavaPaint() { return mJavaPaint; } private static class MultiPointLinearGradientPaint implements Paint { private final static int GRADIENT_SIZE = 100; private final float mX0; private final float mY0; private final float mDx; private final float mDy; private final float mDSize2; private final int[] mColors; private final float[] mPositions; private final TileMode mTile; private int[] mGradient; public MultiPointLinearGradientPaint(float x0, float y0, float x1, float y1, int colors[], float positions[], TileMode tile) { mX0 = x0; mY0 = y0; mDx = x1 - x0; mDy = y1 - y0; mDSize2 = mDx * mDx + mDy * mDy; mColors = colors; mPositions = positions; mTile = tile; } public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { prepareColors(); return new MultiPointLinearGradientPaintContext(cm, deviceBounds, userBounds, xform, hints); } public int getTransparency() { return TRANSLUCENT; } private synchronized void prepareColors() { if (mGradient == null) { // actually create an array with an extra size, so that we can really go // from 0 to SIZE (100%), or currentPos in the loop below will never equal 1.0 mGradient = new int[GRADIENT_SIZE+1]; int prevPos = 0; int nextPos = 1; for (int i = 0 ; i <= GRADIENT_SIZE ; i++) { // compute current position float currentPos = (float)i/GRADIENT_SIZE; while (currentPos > mPositions[nextPos]) { prevPos = nextPos++; } float percent = (currentPos - mPositions[prevPos]) / (mPositions[nextPos] - mPositions[prevPos]); mGradient[i] = getColor(mColors[prevPos], mColors[nextPos], percent); } } } /** * Returns the color between c1, and c2, based on the percent of the distance * between c1 and c2. */ private int getColor(int c1, int c2, float percent) { int a = getChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent); int r = getChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent); int g = getChannel((c1 >> 8) & 0xFF, (c2 >> 8) & 0xFF, percent); int b = getChannel((c1 ) & 0xFF, (c2 ) & 0xFF, percent); return a << 24 | r << 16 | g << 8 | b; } /** * Returns the channel value between 2 values based on the percent of the distance between * the 2 values.. */ private int getChannel(int c1, int c2, float percent) { return c1 + (int)((percent * (c2-c1)) + .5); } private class MultiPointLinearGradientPaintContext implements PaintContext { private ColorModel mColorModel; private final Rectangle mDeviceBounds; private final Rectangle2D mUserBounds; private final AffineTransform mXform; private final RenderingHints mHints; public MultiPointLinearGradientPaintContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xform, RenderingHints hints) { mColorModel = cm; // FIXME: so far all this is always the same rect gotten in getRaster with an indentity matrix? mDeviceBounds = deviceBounds; mUserBounds = userBounds; mXform = xform; mHints = hints; } public void dispose() { } public ColorModel getColorModel() { return mColorModel; } public Raster getRaster(int x, int y, int w, int h) { BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); int[] data = new int[w*h]; if (mDx == 0) { // vertical gradient // compute first column and copy to all other columns int index = 0; for (int iy = 0 ; iy < h ; iy++) { int color = getColor(iy + y, mY0, mDy); for (int ix = 0 ; ix < w ; ix++) { data[index++] = color; } } } else if (mDy == 0) { // horizontal // compute first line in a tmp array and copy to all lines int[] line = new int[w]; for (int ix = 0 ; ix < w ; ix++) { line[ix] = getColor(ix + x, mX0, mDx); } for (int iy = 0 ; iy < h ; iy++) { System.arraycopy(line, 0, data, iy*w, line.length); } } else { int index = 0; for (int iy = 0 ; iy < h ; iy++) { for (int ix = 0 ; ix < w ; ix++) { data[index++] = getColor(ix + x, iy + y); } } } image.setRGB(0 /*startX*/, 0 /*startY*/, w, h, data, 0 /*offset*/, w /*scansize*/); return image.getRaster(); } } /** Returns a color for the easy vertical/horizontal mode */ private int getColor(float absPos, float refPos, float refSize) { float pos = (absPos - refPos) / refSize; return getIndexFromPos(pos); } /** * Returns a color for an arbitrary point. */ private int getColor(float x, float y) { // find the x position on the gradient vector. float _x = (mDx*mDy*(y-mY0) + mDy*mDy*mX0 + mDx*mDx*x) / mDSize2; // from it get the position relative to the vector float pos = (float) ((_x - mX0) / mDx); return getIndexFromPos(pos); } /** * Returns the color based on the position in the gradient. * pos can be anything, even < 0 or > > 1, as the gradient * will use {@link TileMode} value to convert it into a [0,1] value. */ private int getIndexFromPos(float pos) { if (pos < 0.f) { switch (mTile) { case CLAMP: pos = 0.f; break; case REPEAT: // remove the integer part to stay in the [0,1] range // careful: this is a negative value, so use ceil instead of floor pos = pos - (float)Math.ceil(pos); break; case MIRROR: // get the integer and the decimal part // careful: this is a negative value, so use ceil instead of floor int intPart = (int)Math.ceil(pos); pos = pos - intPart; // 0 -> -1 : mirrored order // -1 -> -2: normal order // etc.. // this means if the intpart is even we invert if ((intPart % 2) == 0) { pos = 1.f - pos; } break; } } else if (pos > 1f) { switch (mTile) { case CLAMP: pos = 1.f; break; case REPEAT: // remove the integer part to stay in the [0,1] range pos = pos - (float)Math.floor(pos); break; case MIRROR: // get the integer and the decimal part int intPart = (int)Math.floor(pos); pos = pos - intPart; // 0 -> 1 : normal order // 1 -> 2: mirrored // etc.. // this means if the intpart is odd we invert if ((intPart % 2) == 1) { pos = 1.f - pos; } break; } } int index = (int)((pos * GRADIENT_SIZE) + .5); return mGradient[index]; } } }