了解使用ColorMatrix和ColorMatrixColorFilter来修改Drawable的色调

我正在为一个应用程序的UI工作,我试图使用灰度图标,并允许用户将主题更改为他们select的颜色。 要做到这一点,我试图只是应用某种ColorFilter覆盖绘制顶部的颜色。 我尝试过使用PorterDuff.Mode.MULTIPLY,它的工作原理几乎和我所需要的一样,只不过白色的颜色也覆盖了。 我最理想的东西就是Photoshop中的“颜色”混合模式,graphics保留其透明度和光度,并且只修改图像的颜色。 例如:

替代文字替代文字

做了一些研究后,看起来ColorMatrixColorFilter类可以做我所需要的,但我似乎无法find指向如何使用matrix的任何资源。 这是一个4×5的matrix,但我需要知道的是我如何去devisematrix。 有任何想法吗?

编辑:所以没关系,到目前为止,我发现这个如下:

1 0 0 0 0 //red 0 1 0 0 0 //green 0 0 1 0 0 //blue 0 0 0 1 0 //alpha 

这个matrix是单位matrix(应用时不作任何改变),数字范围从0到1(浮点数)。 该matrix将与每个像素相乘以转换为新的颜色。 所以这就是我开始模糊的地方。 所以我会认为每个像素将是一个1×4的向量,其中包含将与变换matrix点缀的argb值(例如0.2,0.5,0.8,1)。 所以要加倍图像的红色强度,可以使用如下matrix:

 2 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 

这会给你一个向量(颜色) 0.4, 0.5, 0.8, 1 。 从有限的testing来看,这似乎是这种情况,工作正常,但我实际上仍然有同样的问题(即白人获得着色)。 进一步阅读告诉我,这是因为它正在对RGB值进行转换,而对于色相转换,则应先将值转换为HSL值。 所以可能我可以写一个类来读取图像并转换颜色,并用新颜色重新绘制图像。 这与StateListDrawables创build另一个问题,因为我不知道如何去获取这些代码中的每一个,并修改所有这些,以及它将是多么缓慢的过程。 :/

嗯,好吧,所以我想另外一个问题,我会是一个matrix是否可以用来转换RGB到另一个色度信息,如L a b或HSL的色彩空间? 如果是这样的话,我可以将该对话的matrix相乘,然后将色调调整为THATmatrix,然后将该matrix作为ColorFilter应用。

这是我用于我的游戏。 这是网站上各种文章的各个部分的汇编。 学分来自@see链接的原作者。 请注意,颜色matrix可以做更多的事情。 包括反转等…

 public class ColorFilterGenerator { /** * Creates a HUE ajustment ColorFilter * @see http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953 * @see http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html * @param value degrees to shift the hue. * @return */ public static ColorFilter adjustHue( float value ) { ColorMatrix cm = new ColorMatrix(); adjustHue(cm, value); return new ColorMatrixColorFilter(cm); } /** * @see http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953 * @see http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html * @param cm * @param value */ public static void adjustHue(ColorMatrix cm, float value) { value = cleanValue(value, 180f) / 180f * (float) Math.PI; if (value == 0) { return; } float cosVal = (float) Math.cos(value); float sinVal = (float) Math.sin(value); float lumR = 0.213f; float lumG = 0.715f; float lumB = 0.072f; float[] mat = new float[] { lumR + cosVal * (1 - lumR) + sinVal * (-lumR), lumG + cosVal * (-lumG) + sinVal * (-lumG), lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0, lumR + cosVal * (-lumR) + sinVal * (0.143f), lumG + cosVal * (1 - lumG) + sinVal * (0.140f), lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0, lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)), lumG + cosVal * (-lumG) + sinVal * (lumG), lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f }; cm.postConcat(new ColorMatrix(mat)); } protected static float cleanValue(float p_val, float p_limit) { return Math.min(p_limit, Math.max(-p_limit, p_val)); } } 

为了完成这个,我应该添加一个例子:

 ImageView Sun = (ImageView)findViewById(R.id.sun); Sun.setColorFilter(ColorFilterGenerator.adjustHue(162)); // 162 degree rotation 

这里是完整的代码,如果你想调整亮度,对比度,饱和度和色调。 请享用! 非常感谢@RichardLalancette

 public class ColorFilterGenerator { private static double DELTA_INDEX[] = { 0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24, 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8, 10.0 }; /** * @see http://groups.google.com/group/android-developers/browse_thread/thread/9e215c83c3819953 * @see http://gskinner.com/blog/archives/2007/12/colormatrix_cla.html * @param cm * @param value */ public static void adjustHue(ColorMatrix cm, float value) { value = cleanValue(value, 180f) / 180f * (float) Math.PI; if (value == 0){ return; } float cosVal = (float) Math.cos(value); float sinVal = (float) Math.sin(value); float lumR = 0.213f; float lumG = 0.715f; float lumB = 0.072f; float[] mat = new float[] { lumR + cosVal * (1 - lumR) + sinVal * (-lumR), lumG + cosVal * (-lumG) + sinVal * (-lumG), lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0, lumR + cosVal * (-lumR) + sinVal * (0.143f), lumG + cosVal * (1 - lumG) + sinVal * (0.140f), lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0, lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)), lumG + cosVal * (-lumG) + sinVal * (lumG), lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f }; cm.postConcat(new ColorMatrix(mat)); } public static void adjustBrightness(ColorMatrix cm, float value) { value = cleanValue(value,100); if (value == 0) { return; } float[] mat = new float[] { 1,0,0,0,value, 0,1,0,0,value, 0,0,1,0,value, 0,0,0,1,0, 0,0,0,0,1 }; cm.postConcat(new ColorMatrix(mat)); } public static void adjustContrast(ColorMatrix cm, int value) { value = (int)cleanValue(value,100); if (value == 0) { return; } float x; if (value < 0) { x = 127 + (float) value / 100*127; } else { x = value % 1; if (x == 0) { x = (float)DELTA_INDEX[value]; } else { //x = DELTA_INDEX[(p_val<<0)]; // this is how the IDE does it. x = (float)DELTA_INDEX[(value<<0)]*(1-x) + (float)DELTA_INDEX[(value<<0)+1] * x; // use linear interpolation for more granularity. } x = x*127+127; } float[] mat = new float[] { x/127,0,0,0, 0.5f*(127-x), 0,x/127,0,0, 0.5f*(127-x), 0,0,x/127,0, 0.5f*(127-x), 0,0,0,1,0, 0,0,0,0,1 }; cm.postConcat(new ColorMatrix(mat)); } public static void adjustSaturation(ColorMatrix cm, float value) { value = cleanValue(value,100); if (value == 0) { return; } float x = 1+((value > 0) ? 3 * value / 100 : value / 100); float lumR = 0.3086f; float lumG = 0.6094f; float lumB = 0.0820f; float[] mat = new float[] { lumR*(1-x)+x,lumG*(1-x),lumB*(1-x),0,0, lumR*(1-x),lumG*(1-x)+x,lumB*(1-x),0,0, lumR*(1-x),lumG*(1-x),lumB*(1-x)+x,0,0, 0,0,0,1,0, 0,0,0,0,1 }; cm.postConcat(new ColorMatrix(mat)); } protected static float cleanValue(float p_val, float p_limit) { return Math.min(p_limit, Math.max(-p_limit, p_val)); } public static ColorFilter adjustColor(int brightness, int contrast, int saturation, int hue){ ColorMatrix cm = new ColorMatrix(); adjustHue(cm, hue); adjustContrast(cm, contrast); adjustBrightness(cm, brightness); adjustSaturation(cm, saturation); return new ColorMatrixColorFilter(cm); } } 

对于任何对如何使用ColorMatrixColorFilter感兴趣的人。 我在这里使用的示例,当我在canvas上绘制位图时,将每个像素都转换为红色。

该类中的注释来自: http : //developer.android.com/reference/android/graphics/ColorMatrix.html这给你一些有关这是如何工作的见解

 @Override protected void onDraw(Canvas canvas) { // The matrix is stored in a single array, and its treated as follows: [ a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t ] // When applied to a color [r, g, b, a], the resulting color is computed as (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; Paint paint = new Paint(); float[] matrix = { 1, 1, 1, 1, 1, //red 0, 0, 0, 0, 0, //green 0, 0, 0, 0, 0, //blue 1, 1, 1, 1, 1 //alpha }; paint.setColorFilter(new ColorMatrixColorFilter(matrix)); Rect source = new Rect(0, 0, 100, 100); Rect dest = new Rect(0, 0, 100, 100); Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.sampleimage); canvas.drawBitmap(bitmap , source, dest, paint); } 

Hue和RGB之间没有线性关系。 色调是以60°块( http://en.wikipedia.org/wiki/HSL_color_space#General_approach )分段定义的,因此在HSV和RGB之间没有简单的matrix转换。 要更改图像的色调,可以使用以下方法:

 public Bitmap changeHue( Bitmap source, double hue ) { Bitmap result = Bitmap.createBitmap( source.getWidth(), source.getHeight(), source.getConfig() ); float[] hsv = new float[3]; for( int x = 0; x < source.getWidth(); x++ ) { for( int y = 0; y < source.getHeight(); y++ ) { int c = source.getPixel( x, y ); Color.colorToHSV( c, hsv ); hsv[0] = (float) ((hsv[0] + 360 * hue) % 360); c = (Color.HSVToColor( hsv ) & 0x00ffffff) | (c & 0xff000000); result.setPixel( x, y, c ); } } return result; } 

我想这种方法会给你你想要的:

http://android.okhelp.cz/hue-color-colored-filter-bitmap-image-android-example/

 bitmapOrg.setColorFilter(Color.RED, PorterDuff.Mode.MULTIPLY); 

下面的课是对已经发布的答案的改进。 这使得从Bitmap读取和创buildColorFilter变得更容易。

用法示例:

 ImageView imageView = ...; Drawable drawable = imageView.getDrawable(); ColorFilter colorFilter = ColorFilterGenerator.from(drawable).to(Color.RED); imageView.setColorFilter(colorFilter); 

 import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.PictureDrawable; import android.widget.ImageView; /** * Creates a {@link ColorMatrixColorFilter} to adjust the hue, saturation, brightness, or * contrast of an {@link Bitmap}, {@link Drawable}, or {@link ImageView}. * <p/> * Example usage: * <br/> * {@code imageView.setColorFilter(ColorFilterGenerator.from(Color.BLUE).to(Color.RED));} * * @author Jared Rummler <jared.rummler@gmail.com> */ public class ColorFilterGenerator { // Based off answer from StackOverflow // See: http://stackoverflow.com/a/15119089/1048340 private ColorFilterGenerator() { throw new AssertionError(); } public static From from(Drawable drawable) { return new From(drawableToBitmap(drawable)); } public static From from(Bitmap bitmap) { return new From(bitmap); } public static From from(int color) { return new From(color); } // -------------------------------------------------------------------------------------------- private static final double DELTA_INDEX[] = { 0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11, 0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24, 0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42, 0.44, 0.46, 0.48, 0.5, 0.53, 0.56, 0.59, 0.62, 0.65, 0.68, 0.71, 0.74, 0.77, 0.80, 0.83, 0.86, 0.89, 0.92, 0.95, 0.98, 1.0, 1.06, 1.12, 1.18, 1.24, 1.30, 1.36, 1.42, 1.48, 1.54, 1.60, 1.66, 1.72, 1.78, 1.84, 1.90, 1.96, 2.0, 2.12, 2.25, 2.37, 2.50, 2.62, 2.75, 2.87, 3.0, 3.2, 3.4, 3.6, 3.8, 4.0, 4.3, 4.7, 4.9, 5.0, 5.5, 6.0, 6.5, 6.8, 7.0, 7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8, 10.0 }; public static void adjustHue(ColorMatrix cm, float value) { value = cleanValue(value, 180f) / 180f * (float) Math.PI; if (value == 0) { return; } float cosVal = (float) Math.cos(value); float sinVal = (float) Math.sin(value); float lumR = 0.213f; float lumG = 0.715f; float lumB = 0.072f; float[] mat = new float[]{ lumR + cosVal * (1 - lumR) + sinVal * (-lumR), lumG + cosVal * (-lumG) + sinVal * (-lumG), lumB + cosVal * (-lumB) + sinVal * (1 - lumB), 0, 0, lumR + cosVal * (-lumR) + sinVal * (0.143f), lumG + cosVal * (1 - lumG) + sinVal * (0.140f), lumB + cosVal * (-lumB) + sinVal * (-0.283f), 0, 0, lumR + cosVal * (-lumR) + sinVal * (-(1 - lumR)), lumG + cosVal * (-lumG) + sinVal * (lumG), lumB + cosVal * (1 - lumB) + sinVal * (lumB), 0, 0, 0f, 0f, 0f, 1f, 0f, 0f, 0f, 0f, 0f, 1f }; cm.postConcat(new ColorMatrix(mat)); } public static void adjustBrightness(ColorMatrix cm, float value) { value = cleanValue(value, 100); if (value == 0) { return; } float[] mat = new float[]{ 1, 0, 0, 0, value, 0, 1, 0, 0, value, 0, 0, 1, 0, value, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 }; cm.postConcat(new ColorMatrix(mat)); } public static void adjustContrast(ColorMatrix cm, int value) { value = (int) cleanValue(value, 100); if (value == 0) { return; } float x; if (value < 0) { x = 127 + value / 100 * 127; } else { x = value % 1; if (x == 0) { x = (float) DELTA_INDEX[value]; } else { x = (float) DELTA_INDEX[(value << 0)] * (1 - x) + (float) DELTA_INDEX[(value << 0) + 1] * x; } x = x * 127 + 127; } float[] mat = new float[]{ x / 127, 0, 0, 0, 0.5f * (127 - x), 0, x / 127, 0, 0, 0.5f * (127 - x), 0, 0, x / 127, 0, 0.5f * (127 - x), 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 }; cm.postConcat(new ColorMatrix(mat)); } public static void adjustSaturation(ColorMatrix cm, float value) { value = cleanValue(value, 100); if (value == 0) { return; } float x = 1 + ((value > 0) ? 3 * value / 100 : value / 100); float lumR = 0.3086f; float lumG = 0.6094f; float lumB = 0.0820f; float[] mat = new float[]{ lumR * (1 - x) + x, lumG * (1 - x), lumB * (1 - x), 0, 0, lumR * (1 - x), lumG * (1 - x) + x, lumB * (1 - x), 0, 0, lumR * (1 - x), lumG * (1 - x), lumB * (1 - x) + x, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1 }; cm.postConcat(new ColorMatrix(mat)); } // -------------------------------------------------------------------------------------------- private static float cleanValue(float p_val, float p_limit) { return Math.min(p_limit, Math.max(-p_limit, p_val)); } private static float[] getHsv(int color) { float[] hsv = new float[3]; Color.RGBToHSV(Color.red(color), Color.green(color), Color.blue(color), hsv); return hsv; } /** * Converts a {@link Drawable} to a {@link Bitmap} * * @param drawable * The {@link Drawable} to convert * @return The converted {@link Bitmap}. */ private static Bitmap drawableToBitmap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } else if (drawable instanceof PictureDrawable) { PictureDrawable pictureDrawable = (PictureDrawable) drawable; Bitmap bitmap = Bitmap.createBitmap(pictureDrawable.getIntrinsicWidth(), pictureDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); canvas.drawPicture(pictureDrawable.getPicture()); return bitmap; } int width = drawable.getIntrinsicWidth(); width = width > 0 ? width : 1; int height = drawable.getIntrinsicHeight(); height = height > 0 ? height : 1; Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } /** * Calculate the average red, green, blue color values of a bitmap * * @param bitmap * a {@link Bitmap} * @return */ private static int[] getAverageColorRGB(Bitmap bitmap) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); int size = width * height; int[] pixels = new int[size]; int r, g, b; r = g = b = 0; bitmap.getPixels(pixels, 0, width, 0, 0, width, height); for (int i = 0; i < size; i++) { int pixelColor = pixels[i]; if (pixelColor == Color.TRANSPARENT) { size--; continue; } r += Color.red(pixelColor); g += Color.green(pixelColor); b += Color.blue(pixelColor); } r /= size; g /= size; b /= size; return new int[]{ r, g, b }; } /** * Calculate the average color value of a bitmap * * @param bitmap * a {@link Bitmap} * @return */ private static int getAverageColor(Bitmap bitmap) { int[] rgb = getAverageColorRGB(bitmap); return Color.argb(255, rgb[0], rgb[1], rgb[2]); } // Builder // -------------------------------------------------------------------------------------------- public static final class Builder { int hue; int contrast; int brightness; int saturation; public Builder setHue(int hue) { this.hue = hue; return this; } public Builder setContrast(int contrast) { this.contrast = contrast; return this; } public Builder setBrightness(int brightness) { this.brightness = brightness; return this; } public Builder setSaturation(int saturation) { this.saturation = saturation; return this; } public ColorFilter build() { ColorMatrix cm = new ColorMatrix(); adjustHue(cm, hue); adjustContrast(cm, contrast); adjustBrightness(cm, brightness); adjustSaturation(cm, saturation); return new ColorMatrixColorFilter(cm); } } public static final class From { final int oldColor; private From(Bitmap bitmap) { oldColor = getAverageColor(bitmap); } private From(int oldColor) { this.oldColor = oldColor; } public ColorFilter to(int newColor) { float[] hsv1 = getHsv(oldColor); float[] hsv2 = getHsv(newColor); int hue = (int) (hsv2[0] - hsv1[0]); int saturation = (int) (hsv2[1] - hsv1[1]); int brightness = (int) (hsv2[2] - hsv1[2]); return new ColorFilterGenerator.Builder() .setHue(hue) .setSaturation(saturation) .setBrightness(brightness) .build(); } } } 

和其余答案一样,我使用了一个颜色matrix来实现这个行为,但是你可以传入一个常规的Android颜色资源。 matrix将颜色映射到图像值和白色之间的范围内。

 /** * Color everything that isn't white, the tint color * @param tintColor the color to tint the icon */ public void setInverseMultiplyFilter(Drawable imgCopy, @ColorInt int tintColor) { Drawable imgCopy = imageView.getDrawable().getConstantState().newDrawable(); float colorRed = Color.red(tintColor) / 255f; float colorGreen = Color.green(tintColor) / 255f; float colorBlue = Color.blue(tintColor) / 255f; imgCopy.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(new float[]{ 1 - colorRed, 0, 0, 0, colorRed * 255, 0, 1 - colorGreen, 0, 0, colorGreen * 255, 0, 0, 1 - colorBlue, 0, colorBlue * 255, 0, 0, 0, Color.alpha(tintColor) / 255f, 0, }))); imageView.setImageDrawable(imgCopy); imageView.invalidate(); } 

我做了一个小的ColorMatrixFiltertesting器,基于以下片段:

 private Bitmap setColorFilter(Bitmap drawable) { Bitmap grayscale = Bitmap.createBitmap(drawable.getWidth(), drawable.getHeight(), drawable.getConfig()); //if(isRenderMode) bOriginal.recycle(); Canvas c = new Canvas(grayscale ); Paint p = new Paint(); final ColorMatrix matrixA = new ColorMatrix(); matrixA.setSaturation(sauturationValue/2); float[] mx = { r1Value, r2Value, r3Value, r4Value, r5Value, g1Value, g2Value, g3Value, g4Value, g5Value, b1Value, b2Value, b3Value, b4Value, b5Value, a1Value, a2Value, a3Value, a4Value, a5Value }; final ColorMatrix matrixB = new ColorMatrix(mx); matrixA.setConcat(matrixB, matrixA); final ColorMatrixColorFilter filter = new ColorMatrixColorFilter(matrixA); p.setColorFilter(filter); c.drawBitmap(drawable, 0, 0, p); return grayscale; } 

您可以在这里查看: https : //play.google.com/store/apps/details?id = org.vaelostudio.colormatrixtester

即使通过使用ColorMatrix可以实现许多有用的效果,我个人会考虑使用ColorMap[]ImageAttributes 。 通过这样做,我们可以定义哪些颜色应该被replace为哪种颜色。