Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 374abf0b authored by Xavier Ducrohet's avatar Xavier Ducrohet
Browse files

ADT/Layout: support for 3+ color in linear gradients

Change-Id: I14c6a5a1de41470c6f1c66d490492ecc727302f2
parent 5e083024
Loading
Loading
Loading
Loading
+243 −6
Original line number Diff line number Diff line
@@ -19,10 +19,20 @@ package android.graphics;
import java.awt.GradientPaint;
import java.awt.Color;
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.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;

public class LinearGradient extends Shader {

    private GradientPaint mGradientPaint;
    private Paint mJavaPaint;

    /**
     * Create a shader that draws a linear gradient along a line.
@@ -46,12 +56,13 @@ public class LinearGradient extends Shader {
            throw new IllegalArgumentException("color and position arrays must be of equal length");
        }

        // FIXME implement multi color linear gradient
        if (colors.length == 2) {
        if (colors.length == 2) { // for 2 colors: use the Java implementation
            // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
            // If true the alpha is read from the int.
            mGradientPaint = new GradientPaint(x0, y0, new Color(colors[0], true /* hasalpha */),
            mJavaPaint = new GradientPaint(x0, y0, new Color(colors[0], true /* hasalpha */),
                    x1, y1, new Color(colors[1], true /* hasalpha */), tile != TileMode.CLAMP);
        } else {
            mJavaPaint = new MultiPointLinearGradientPaint(x0, y0, x1, y1, colors, positions, tile);
        }
    }

@@ -70,7 +81,7 @@ public class LinearGradient extends Shader {
            TileMode tile) {
        // The hasAlpha flag in Color() is only used to enforce alpha to 0xFF if false.
        // If true the alpha is read from the int.
        mGradientPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */), x1, y1,
        mJavaPaint = new GradientPaint(x0, y0, new Color(color0, true /* hasalpha */), x1, y1,
                new Color(color1, true /* hasalpha */), tile != TileMode.CLAMP);
    }

@@ -78,6 +89,232 @@ public class LinearGradient extends Shader {

    @Override
    public Paint getJavaPaint() {
        return mGradientPaint;
        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) {
                SampleModel sampleModel = mColorModel.createCompatibleSampleModel(w, h);
                WritableRaster raster = Raster.createWritableRaster(sampleModel,
                        new java.awt.Point(x, y));

                DataBuffer data = raster.getDataBuffer();

                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.setElem(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);
                    }

                    int index = 0;
                    for (int iy = 0 ; iy < h ; iy++) {
                        for (int ix = 0 ; ix < w ; ix++) {
                            data.setElem(index++, line[ix]);
                        }
                    }
                } else {
                    int index = 0;
                    for (int iy = 0 ; iy < h ; iy++) {
                        for (int ix = 0 ; ix < w ; ix++) {
                            data.setElem(index++, getColor(ix + x, iy + y));
                        }
                    }
                }

                return raster;
            }
        }

        /** 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.
         * <var>pos</var> can be anything, even &lt; 0 or &gt; > 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];
        }
    }
}