android drawable colormatrix colorfilter

android - Comprender el uso de ColorMatrix y ColorMatrixColorFilter para modificar el tono de un Drawable



(9)

Estoy trabajando en una interfaz de usuario para una aplicación, y estoy tratando de usar iconos de escala de grises, y permitir que el usuario cambie el tema al color que elija. Para hacer esto, intento simplemente aplicar un ColorFilter de algún tipo para superponer un color en la parte superior del dibujo. He intentado usar PorterDuff.Mode.MULTIPLY, y funciona casi exactamente como necesito, excepto que los blancos se superponen con el color también. Lo que idealmente busco es algo así como el modo de fusión "Color" en Photoshop, donde el gráfico conserva su transparencia y luminosidad, y solo modifica el color de la imagen. Por ejemplo:

se convierte

Después de investigar un poco, parece que la clase ColorMatrixColorFilter puede hacer lo que necesito, pero parece que no puedo encontrar ningún recurso que indique cómo se usa la matriz. Es una matriz 4x5, pero lo que necesito saber es cómo voy a diseñar la matriz. ¿Algunas ideas?

EDITAR: Muy bien, lo que he encontrado hasta ahora sobre esto es el siguiente:

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

Donde esta matriz es la matriz de identidad (cuando se aplica, no hace cambios), y los números van de 0 a 1 (flotantes). Esta matriz se multiplicará con cada píxel para convertir al nuevo color. Así que aquí es donde comienza a ser borroso para mí. Entonces, creo que cada píxel sería un vector de 1 x 4 que contiene los valores de argb (por ejemplo 0.2, 0.5, 0.8, 1 ) que estarían salpicados con la matriz de transformación. Entonces, para duplicar la intensidad roja de una imagen, usaría una matriz como:

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

que le daría un vector (color) de 0.4, 0.5, 0.8, 1 . A partir de pruebas limitadas, este parece ser el caso, y funciona correctamente, pero en realidad todavía termino con el mismo problema (es decir, los blancos ganan color). Lectura adicional me dice que esto se debe a que está haciendo la conversión en valores RGB, mientras que para el cambio de matiz, los valores primero deben convertirse a valores HSL. Así que posiblemente podría escribir una clase que leería la imagen y convertiría los colores, y volvería a dibujar la imagen con los nuevos colores. Esto crea OTRO problema con StateListDrawables, ya que no estoy seguro de cómo conseguiría cada uno de estos códigos y los modificaría a todos, y qué tan lento sería el proceso. : /

Hmm, está bien, entonces supongo que otra pregunta que tengo es si una matriz se puede usar para convertir RGB a otro espacio de color con información de luminosidad, como L a b o HSL? Si es así, podría simplemente multiplicar la matriz para esa conversación, luego hacer el ajuste de matiz a ESA matriz, luego aplicar esa matriz como el ColorFilter.


A pesar de que se pueden lograr muchos efectos útiles usando ColorMatrix yo personalmente consideraría usar un ColorMap[] junto con ImageAttributes . Al hacer esto, podemos definir qué colores deben reemplazarse con qué colores.


Al igual que el resto de las respuestas, utilicé una matriz de colores para implementar este comportamiento, pero puede pasar un recurso de color de Android normal. La matriz, mapea el color en un rango entre el valor de la imagen y el blanco.

/** * 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(); }



Esto es lo que uso para mi juego. Esta es la compilación de varias partes encontradas en varios artículos en sitios web. Los créditos van al autor original de los enlaces @see. Tenga en cuenta que se puede hacer mucho más con las matrices de color. Incluyendo inversión, etc ...

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)); } }

Para completar esto, debería agregar un ejemplo:

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


Hice un pequeño probador ColorMatrixFilter, basado en el siguiente fragmento:

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; }

Puede consultarlo aquí: https://play.google.com/store/apps/details?id=org.vaelostudio.colormatrixtester


La siguiente clase es una mejora en las respuestas que ya se han publicado. Esto facilita la lectura y creación de un ColorFilter partir de un Bitmap .

Ejemplo de uso:

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 <[email protected]> */ public class ColorFilterGenerator { // Based off answer from // See: http://.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(); } } }


No hay una relación lineal entre Hue y RGB. Hue se define por partes en trozos de 60 ° ( http://en.wikipedia.org/wiki/HSL_color_space#General_approach ), y por lo tanto no hay una conversión de matriz simple entre HSV y RGB. Para cambiar el tono de una imagen, puede usar el siguiente método:

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; }


Para cualquiera que esté interesado en cómo usar ColorMatrixColorFilter. La muestra que utilicé aquí convirtió cada píxel en rojo cuando dibujo el mapa de bits en el lienzo.

El comentario en la clase es de: http://developer.android.com/reference/android/graphics/ColorMatrix.html esto le da algunas ideas sobre cómo está funcionando esto

@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); }


aquí está el código completo si desea ajustar el brillo, el contraste, la saturación y el tono. ¡Disfrutar! Muchas gracias a @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); } }