blob: 8aed4b41fb0ca46d17d1a74aeebdd552845edc3b [file] [log] [blame]
/*
* Copyright (C) 2010 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 android.graphics.Shader.TileMode;
import java.util.Arrays;
/**
* Base class for true Gradient shader delegate.
*/
public abstract class Gradient_Delegate extends Shader_Delegate {
protected final int[] mColors;
protected final float[] mPositions;
@Override
public boolean isSupported() {
// all gradient shaders are supported.
return true;
}
@Override
public String getSupportMessage() {
// all gradient shaders are supported, no need for a gradient support
return null;
}
/**
* Creates the base shader and do some basic test on the parameters.
*
* @param nativeMatrix reference to the shader's native transformation matrix
* @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.
*/
protected Gradient_Delegate(long nativeMatrix, long[] colors, float[] positions) {
super(nativeMatrix);
assert colors.length >= 2 : "needs >= 2 number of colors";
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;
}
} else {
assert colors.length == positions.length :
"color and position " + "arrays must be of equal length";
positions[0] = Math.min(Math.max(0, positions[0]), 1);
for (int i = 1; i < positions.length; i++) {
positions[i] = Math.min(Math.max(positions[i-1], positions[i]), 1);
}
}
mColors = Arrays.stream(colors).mapToInt(Color::toArgb).toArray();
mPositions = positions;
}
/**
* Base class for (Java) Gradient Paints. This handles computing the gradient colors based
* on the color and position lists, as well as the {@link TileMode}
*
*/
protected abstract static class GradientPaint implements java.awt.Paint {
private final static int GRADIENT_SIZE = 100;
private final int[] mColors;
private final float[] mPositions;
private final TileMode mTileMode;
private int[] mGradient;
protected GradientPaint(int[] colors, float[] positions, TileMode tileMode) {
mColors = colors;
mPositions = positions;
mTileMode = tileMode;
}
@Override
public int getTransparency() {
return java.awt.Paint.TRANSLUCENT;
}
/**
* Pre-computes the colors for the gradient. This must be called once before any call
* to {@link #getGradientColor(float)}
*/
protected void precomputeGradientColors() {
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;
if (currentPos < mPositions[0]) {
mGradient[i] = mColors[0];
continue;
}
while (nextPos < mPositions.length && currentPos >= mPositions[nextPos]) {
prevPos = nextPos++;
}
if (nextPos == mPositions.length || currentPos == prevPos) {
mGradient[i] = mColors[prevPos];
} else {
float percent = (currentPos - mPositions[prevPos]) /
(mPositions[nextPos] - mPositions[prevPos]);
mGradient[i] = computeColor(mColors[prevPos], mColors[nextPos], percent);
}
}
}
}
/**
* 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.
*/
protected int getGradientColor(float pos) {
if (pos < 0.f) {
if (mTileMode != null) {
switch (mTileMode) {
case CLAMP:
pos = 0.f;
break;
case REPEAT:
// remove the integer part to stay in the [0,1] range.
// we also need to invert the value from [-1,0] to [0, 1]
pos = pos - (float)Math.floor(pos);
break;
case MIRROR:
// this is the same as the positive side, just make the value positive
// first.
pos = Math.abs(pos);
// 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;
}
} else {
pos = 0.0f;
}
} else if (pos > 1f) {
if (mTileMode != null) {
switch (mTileMode) {
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;
}
} else {
pos = 1.0f;
}
}
int index = (int)((pos * GRADIENT_SIZE) + .5);
return mGradient[index];
}
/**
* Returns the color between c1, and c2, based on the percent of the distance
* between c1 and c2.
*/
private int computeColor(int c1, int c2, float percent) {
int a = computeChannel((c1 >> 24) & 0xFF, (c2 >> 24) & 0xFF, percent);
int r = computeChannel((c1 >> 16) & 0xFF, (c2 >> 16) & 0xFF, percent);
int g = computeChannel((c1 >> 8) & 0xFF, (c2 >> 8) & 0xFF, percent);
int b = computeChannel((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 computeChannel(int c1, int c2, float percent) {
return c1 + (int)((percent * (c2-c1)) + .5);
}
}
}