by Chiu-Ki Chan
LinearGradient
,
RadialGradient
,
SweepGradient
,
BitmapGradient
,
ComposeGradient
LightingColorFilter
,
ColorMatrixColorFilter
,
PorterDuffColorFilter
BlurMaskFilter
,
EmbossMaskFilter
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
type="rectangle">
<gradient
android:startColor="@color/blue"
android:centerColor="@color/white"
android:endColor="@color/red"
android:angle="45"/>
</shape>
LinearGradient extends Shader
public class RainbowTextView extends TextView {
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
int[] rainbow = getRainbowColors();
Shader shader = new LinearGradient(0, 0, 0, w, rainbow,
null, Shader.TileMode.MIRROR);
Matrix matrix = new Matrix();
matrix.setRotate(90);
shader.setLocalMatrix(matrix);
getPaint().setShader(shader);
}
private int[] getRainbowColors() {
return new int[] {
getResources().getColor(R.color.rainbow_red),
getResources().getColor(R.color.rainbow_yellow),
getResources().getColor(R.color.rainbow_green),
getResources().getColor(R.color.rainbow_blue),
getResources().getColor(R.color.rainbow_purple)
};
}
}
<bitmap
xmlns:android="http://schemas.android.com/apk/res/android"
android:src="@drawable/cheetah_tile"
android:tileMode="repeat"/>
BitmapShader extends Shader
Bitmap bitmap = BitmapFactory.decodeResource(
getResources(), R.drawable.cheetah_tile);
Shader shader = new BitmapShader(bitmap,
Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
textView.getPaint().setShader(shader);
CLAMP
|
REPEAT
|
MIRROR
|
public class PeekThroughImageView extends ImageView {
private final float radius;
private Paint paint = null;
private float x;
private float y;
private boolean shouldDrawOpening = false;
public boolean onTouchEvent(MotionEvent motionEvent) {
int action = motionEvent.getAction();
shouldDrawOpening = (action == MotionEvent.ACTION_DOWN ||
action == MotionEvent.ACTION_MOVE);
x = motionEvent.getX();
y = motionEvent.getY();
invalidate();
return true;
}
}
protected void onDraw(Canvas canvas) {
if (paint == null) {
Bitmap original = Bitmap.createBitmap(
getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas originalCanvas = new Canvas(original);
super.onDraw(originalCanvas); // ImageView
Shader shader = new BitmapShader(original,
Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
paint = new Paint();
paint.setShader(shader);
}
canvas.drawColor(Color.GRAY);
if (shouldDrawOpening) {
canvas.drawCircle(x, y - radius, radius, paint);
}
}
[ a, b, c, d, e,
f, g, h, i, j,
k, l, m, n, o,
p, q, r, s, t ]
Apply to [R, G, B, A], after clamping:
R' = a*R + b*G + c*B + d*A + e;
G' = f*R + g*G + h*B + i*A + j;
B' = k*R + l*G + m*B + n*A + o;
A' = p*R + q*G + r*B + s*A + t;
Identity:
[ R, [ 1, 0, 0, 0, 0, [ R,
G, * 0, 1, 0, 0, 0, = G,
B, 0, 0, 1, 0, 0, B,
A ] 0, 0, 0, 1, 0 ] A ]
Bitmap bitmap = Bitmap.createBitmap(original.getWidth(),
original.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
paint.setColorFilter(new ColorMatrixColorFilter(
getColorMatrix()));
canvas.drawBitmap(original, 0, 0, paint);
return bitmap;
private ColorMatrix getColorMatrix() {
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
return colorMatrix;
}
colorMatrix.setSaturation(0)
[ 0.213, 0.715, 0.072, 0, 0,
0.213, 0.715, 0.072, 0, 0,
0.213, 0.715, 0.072, 0, 0,
0, 0, 0, 1, 0 ]
// 0.213 + 0.715 + 0.072 = 1
private ColorMatrix getColorMatrix() {
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
ColorMatrix colorScale = new ColorMatrix();
colorScale.setScale(1, 1, 0.8f, 1);
// Convert to grayscale, then apply brown color
colorMatrix.postConcat(colorScale);
return colorMatrix;
}
colorMatrix.setScale(1, 1, 0.8f, 1)
[ 1, 0, 0, 0, 0,
0, 1, 0, 0, 0,
0, 0, 0.8, 0, 0,
0, 0, 0, 1, 0 ]
private ColorMatrix getColorMatrix() {
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0);
float m = 255f;
float t = -255*128f;
ColorMatrix threshold = new ColorMatrix(new float[] {
m, 0, 0, 1, t,
0, m, 0, 1, t,
0, 0, m, 1, t,
0, 0, 0, 1, 0
});
// Convert to grayscale, then scale and clamp
colorMatrix.postConcat(threshold);
return colorMatrix;
}
private ColorMatrix getColorMatrix() {
return new ColorMatrix(new float[] {
-1, 0, 0, 0, 255,
0, -1, 0, 0, 255,
0, 0, -1, 0, 255,
0, 0, 0, 1, 0
});
}
private ColorMatrix getColorMatrix() {
return new ColorMatrix(new float[] {
0, 0, 0, 0, 0,
0.3f, 0, 0, 0, 50,
0, 0, 0, 0, 255,
0.2f, 0.4f, 0.4f, 0, -30
});
}
private ColorMatrix getColorMatrix() {
return new ColorMatrix(new float[] {
0, 0, 0, 0, 255,
0, 0, 0, 0, 0,
0.2f, 0, 0, 0, 50,
0.2f, 0.2f, 0.2f, 0, -20
});
}
/** Create a colorfilter that multiplies the RGB channels by one color,
and then adds a second color. */
LightingColorFilter(int mul, int add)
R' = R * mul.R + add.R
G' = G * mul.G + add.G
B' = B * mul.B + add.B
Little brother of ColorMatrixColorFilter
[ mul.R, 0, 0, 0, add.R
0, mul.G, 0, 0, add.G,
0, 0, mul.B, 0, add.B,
0, 0, 0, 1, 0 ]
mul.R = Color.red(mul) / 255f
e.g. #ff0000 → 0xff / 255 = 255 / 255 = 1
Four colored quarters
public class FourColorsImageView extends ImageView {
private Bitmap bitmap = null;
protected void onDraw(Canvas canvas) {
if (bitmap == null) {
Bitmap quarter = Bitmap.createBitmap(
getWidth()/2, getHeight()/2, Bitmap.Config.ARGB_8888);
Canvas quarterCanvas = new Canvas(quarter);
quarterCanvas.scale(0.5f, 0.5f);
super.onDraw(quarterCanvas);
quarterCanvas.scale(2, 2);
createBitmap(quarter);
quarter.recycle();
}
canvas.drawBitmap(bitmap, 0, 0, null);
}
}
private void createBitmap(Bitmap quarter) {
bitmap = Bitmap.createBitmap(
getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
Paint paint = new Paint();
// Top left
paint.setColorFilter(new LightingColorFilter(Color.RED, 0));
canvas.drawBitmap(quarter, 0, 0, paint);
// Top right
paint.setColorFilter(new LightingColorFilter(Color.YELLOW, 0));
canvas.drawBitmap(quarter, getWidth()/2, 0, paint);
// Bottom left
paint.setColorFilter(new LightingColorFilter(Color.BLUE, 0));
canvas.drawBitmap(quarter, 0, getHeight()/2, paint);
// Bottom right
paint.setColorFilter(new LightingColorFilter(Color.GREEN, 0));
canvas.drawBitmap(quarter, getWidth()/2, getHeight()/2, paint);
}
DST
(what was already there)
SRC
(what we are drawing)
Bitmap bitmap = Bitmap.createBitmap(
original.getWidth(), original.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
// Draw the original bitmap (DST during Porter-Duff transfer)
canvas.drawBitmap(original, 0, 0, null);
// DST_IN = Whatever was there, keep the part that overlaps
// with what I'm drawing now
Paint maskPaint = new Paint();
maskPaint.setXfermode(
new PorterDuffXfermode(PorterDuff.Mode.DST_IN));
canvas.drawBitmap(mask, 0, 0, maskPaint);
DST
(what was already there)
SRC
(what we are drawing)
DST
(what was already there)
SRC
(what we are drawing)
Paint overPaint = new Paint();
// DST_OVER = Whatever was there (DST), put it over what
// we are drawing now
overPaint.setXfermode(
new PorterDuffXfermode(PorterDuff.Mode.DST_OVER));
// Draw original (SRC) with dim filter
overPaint.setColorFilter(createDimFilter());
canvas.drawBitmap(original, 0, 0, overPaint);
DST
(what was already there)
SRC
(what we are drawing)
private ColorFilter createDimFilter() {
ColorMatrix colorMatrix = new ColorMatrix();
colorMatrix.setSaturation(0f);
float scale = 0.5f;
colorMatrix.setScale(scale, scale, scale, 1f);
return new ColorMatrixColorFilter(colorMatrix);
}
DST
(what was already there)
SRC
(what we are drawing)
int strokeWidth = getResources().getDimensionPixelSize(
R.dimen.dashed_text_stroke_width);
final HollowSpan span = new HollowSpan(strokeWidth);
String text = textView.getText().toString();
final SpannableString spannableString = new SpannableString(text);
spannableString.setSpan(span, 0, text.length(), 0);
private static class HollowSpan extends ReplacementSpan {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path path = new Path();
private int width;
public HollowSpan(int strokeWidth) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
}
public void setPathEffect(PathEffect effect) {
paint.setPathEffect(effect);
}
}
public int getSize(Paint paint, CharSequence text,
int start, int end, Paint.FontMetricsInt fm) {
this.paint.setColor(paint.getColor());
width = (int) (paint.measureText(text, start, end) +
this.paint.getStrokeWidth());
return width;
}
public void draw(
Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
path.reset();
paint.getTextPath(text.toString(), start, end, x, y, path);
path.close();
canvas.translate(this.paint.getStrokeWidth() / 2, 0);
canvas.drawPath(path, this.paint);
canvas.translate(-this.paint.getStrokeWidth() / 2, 0);
}
PathEffect dash = new DashPathEffect(
new float[] { strokeWidth * 3, strokeWidth }, 0);
span.setPathEffect(dash);
private static class HollowSpan extends ReplacementSpan {
private final Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final Path path = new Path();
private int width;
public HollowSpan(int strokeWidth) {
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(strokeWidth);
}
public void setPathEffect(PathEffect effect) {
paint.setPathEffect(effect);
}
// In draw, canvas.drawPath(path, this.paint);
}
PathEffect dash = new DashPathEffect(
new float[] { strokeWidth * 3, strokeWidth }, 0);
PathEffect corner = new CornerPathEffect(strokeWidth);
PathEffect effect = new ComposePathEffect(dash, corner);
span.setPathEffect(effect);
private PathEffect getTrianglePathEffect(int strokeWidth) {
return new PathDashPathEffect(
getTriangle(strokeWidth),
strokeWidth,
0.0f,
PathDashPathEffect.Style.ROTATE);
}
private Path getTriangle(float size) {
Path path = new Path();
float half = size / 2;
path.moveTo(-half, -half);
path.lineTo(half, -half);
path.lineTo(0, half);
path.close();
return path;
}
private void applyFilter(
TextView textView, float[] direction, float ambient,
float specular, float blurRadius) {
if (Build.VERSION.SDK_INT >= 11) {
textView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
EmbossMaskFilter filter = new EmbossMaskFilter(
direction, ambient, specular, blurRadius);
textView.getPaint().setMaskFilter(filter);
}
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
(Not supported by hardware acceleration)
applyFilter(emboss, new float[] { 0f, 1f, 0.5f }, 0.8f, 3f, 3f);
applyFilter(deboss, new float[] { 0f, -1f, 0.5f }, 0.8f, 15f, 1f);
private void applyFilter(
TextView textView, BlurMaskFilter.Blur style) {
if (Build.VERSION.SDK_INT >= 11) {
textView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
textView.setText(style.name());
float radius = textView.getTextSize() / 10;
BlurMaskFilter filter = new BlurMaskFilter(radius, style);
textView.getPaint().setMaskFilter(filter);
}
@TargetApi(17)
private Bitmap blur(Bitmap original, float radius) {
Bitmap bitmap = Bitmap.createBitmap(
original.getWidth(), original.getHeight(),
Bitmap.Config.ARGB_8888);
RenderScript rs = RenderScript.create(this);
Allocation allocIn = Allocation.createFromBitmap(rs, original);
Allocation allocOut = Allocation.createFromBitmap(rs, bitmap);
ScriptIntrinsicBlur blur = ScriptIntrinsicBlur.create(
rs, Element.U8_4(rs));
blur.setInput(allocIn);
blur.setRadius(radius);
blur.forEach(allocOut);
allocOut.copyTo(bitmap);
rs.destroy();
return bitmap;
}
private Bitmap convolve(Bitmap original, float[] coefficients) {
Bitmap bitmap = Bitmap.createBitmap(
original.getWidth(), original.getHeight(),
Bitmap.Config.ARGB_8888);
RenderScript rs = RenderScript.create(this);
Allocation allocIn = Allocation.createFromBitmap(rs, original);
Allocation allocOut = Allocation.createFromBitmap(rs, bitmap);
ScriptIntrinsicConvolve3x3 convolution
= ScriptIntrinsicConvolve3x3.create(rs, Element.U8_4(rs));
convolution.setInput(allocIn);
convolution.setCoefficients(coefficients);
convolution.forEach(allocOut);
allocOut.copyTo(bitmap); // { 1, 1, 1,
re.destroy(); // 1, 1, 1,
return bitmap; // 1, 1, 1 } / 9
}
private Bitmap convolve(Bitmap original, float[] coefficients) {
Bitmap bitmap = Bitmap.createBitmap(
original.getWidth(), original.getHeight(),
Bitmap.Config.ARGB_8888);
RenderScript rs = RenderScript.create(this);
Allocation allocIn = Allocation.createFromBitmap(rs, original);
Allocation allocOut = Allocation.createFromBitmap(rs, bitmap);
ScriptIntrinsicConvolve3x3 convolution
= ScriptIntrinsicConvolve3x3.create(rs, Element.U8_4(rs));
convolution.setInput(allocIn);
convolution.setCoefficients(coefficients);
convolution.forEach(allocOut);
allocOut.copyTo(bitmap); // { 0, -1, 0,
re.destroy(); // -1 , 5, -1,
return bitmap; // 0, -1, 0 }
}
private Bitmap convolve(Bitmap original, float[] coefficients) {
Bitmap bitmap = Bitmap.createBitmap(
original.getWidth(), original.getHeight(),
Bitmap.Config.ARGB_8888);
RenderScript rs = RenderScript.create(this);
Allocation allocIn = Allocation.createFromBitmap(rs, original);
Allocation allocOut = Allocation.createFromBitmap(rs, bitmap);
ScriptIntrinsicConvolve3x3 convolution
= ScriptIntrinsicConvolve3x3.create(rs, Element.U8_4(rs));
convolution.setInput(allocIn);
convolution.setCoefficients(coefficients);
convolution.forEach(allocOut);
allocOut.copyTo(bitmap); // { -1, -1, -1,
re.destroy(); // -1 , 8, -1,
return bitmap; // -1, -1, -1 }
}
private Bitmap convolve(Bitmap original, float[] coefficients) {
Bitmap bitmap = Bitmap.createBitmap(
original.getWidth(), original.getHeight(),
Bitmap.Config.ARGB_8888);
RenderScript rs = RenderScript.create(this);
Allocation allocIn = Allocation.createFromBitmap(rs, original);
Allocation allocOut = Allocation.createFromBitmap(rs, bitmap);
ScriptIntrinsicConvolve3x3 convolution
= ScriptIntrinsicConvolve3x3.create(rs, Element.U8_4(rs));
convolution.setInput(allocIn);
convolution.setCoefficients(coefficients);
convolution.forEach(allocOut);
allocOut.copyTo(bitmap); // { 0, 20, 0,
re.destroy(); // 20, -59, 20,
return bitmap; // 1, 13, 0 } / 7
}