Understanding the Use of ColorMatrix and ColorMatrixColorFilter to Modify a Drawable's Hue

AndroidDrawableColormatrixColorfilter

Android Problem Overview


I'm working on a UI for an app, and I'm attempting to use grayscale icons, and allow the user to change the theme to a color of their choosing. To do this, I'm trying to just apply a ColorFilter of some sort to overlay a color on top of the drawable. I've tried using PorterDuff.Mode.MULTIPLY, and it works almost exactly as I need, except that whites get overlayed with the color as well. What I'm ideally looking for is something like the "Color" blending mode in Photoshop, where the graphic retains its transparency and luminosity, and only modifies the color of the image. For example:

alt text becomes alt text

After doing some research, it appears that the ColorMatrixColorFilter class may do what I need, but I can't seem to find any resources pointing to how the matrix is used. It's a 4x5 matrix, but what I need to know is how I go about designing the matrix. Any ideas?

EDIT: So okay, what I've found so far on this is as follows:

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

Where this matrix is the identity matrix (when applied, makes no changes), and the numbers range from 0 to 1 (floats). This matrix will be multiplied with each pixel to convert to the new color. So this is where it starts to get fuzzy for me. So I would think each pixel would be a 1 x 4 vector containing the argb values (e.g. 0.2, 0.5, 0.8, 1) that would be dotted with the transformation matrix. So to double the red intensity of an image, you would use a matrix such as:

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

which would give you a vector (color) of 0.4, 0.5, 0.8, 1. From limited testing, this seems to be the case, and works properly, but I actually still end up with the same problem (i.e. whites gain coloring). Further reading tells me that this is because it's doing the conversion on RGB values, whereas for hue shifting, the values should first be converted to HSL values. So possibly I could write a class that would read the image and convert the colors, and redraw the image with the new colors. This creates ANOTHER problem with StateListDrawables, as I'm not sure how I would go about getting each of these in code and modifying all of them, and how slow a process it would be. :/

Hmm, okay, so I suppose another question I would have is whether a matrix can be used to convert RGB to another color space with luminosity information, such as Lab or HSL? If so, I could just multiply the matrix for that converstion, then make the hue adjustment to THAT matrix, then apply that matrix as the ColorFilter.

Android Solutions


Solution 1 - Android

This is what I use for my game. This is the compilation of various part found on various articles on websites. Credits goes to the original author from the @see links. Note that a lot more can be done with color matrices. Including inverting, 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));
}
}

To complete this I should add an example:

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

Solution 2 - Android

here is the complete code if you want to adjust the bright, contrast, saturation and hue. Enjoy! Thanks a lot to @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);
}
}

Solution 3 - Android

For anyone who is interested in how to use the ColorMatrixColorFilter. The sample I used here, converted every pixel into red when I draw the bitmap on the canvas.

The comment in the class is from: http://developer.android.com/reference/android/graphics/ColorMatrix.html this gives you some insights on how this is working

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

Solution 4 - Android

The below class is an improvement on the answers that have already been posted. This makes it easier to read and create a ColorFilter from a Bitmap.

Example usage:

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

}

Solution 5 - Android

There is no linear relationship between Hue and RGB. Hue is defined piecewise in 60° chunks (<http://en.wikipedia.org/wiki/HSL_color_space#General_approach>;), and therefore there is not a simple matrix conversion between HSV and RGB. To change the hue of an image, you can use the following method :

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

Solution 6 - Android

I think this method will give you what you want:

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

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

Solution 7 - Android

Like the rest of the answers I used a color matrix to implement this behavior, but you can pass in a regular Android color resource. The matrix, maps the color into a range between the image value and white.

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

Solution 8 - Android

I made a little ColorMatrixFilter tester, based on the following snippet:

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

You can check it out here: https://play.google.com/store/apps/details?id=org.vaelostudio.colormatrixtester

Solution 9 - Android

Even though a lot of useful effects can be achieved by using the ColorMatrix I personally would consider using a ColorMap[] along with ImageAttributes. By doing this we can define which colors should be replaced with which colors.

Solution 10 - Android

The way I solved it is to start with a gray scale image.

original ---> grayImage

you can do it easily in photoshop, or if you need to do it in code you can use the following method.

private fun changeImageToGreyScale() {
   val matrix = ColorMatrix()
   matrix.setSaturation(0f)

   val originalBitmap = BitmapFactory.decodeResource(resources, originalImageID)
   val bitmapCopy = Bitmap.createBitmap(originalBitmap.width, 
   originalBitmap.height, Bitmap.Config.ARGB_8888)
   val canvas = Canvas(bitmapCopy)

   val paint = Paint()
   val colorFilter = ColorMatrixColorFilter(matrix)
   paint.colorFilter = colorFilter
   canvas.drawBitmap(originalBitmap, 0f, 0f, paint)
   grayScaleImage = bitmapCopy
}

After you have your GrayScale image you can use the PorterDuff OVERLAY mode to add the desired Color.

private fun applyFilterToImage() {

    val bitmapCopy = Bitmap.createBitmap(grayScaleImage.width, grayScaleImage.height, Bitmap.Config.ARGB_8888)
    val canvas = Canvas(bitmapCopy)

    val rnd = java.util.Random()
    val randomColor = Color.rgb(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256))

    val paint = Paint()
    val porterDuffMode = PorterDuff.Mode.OVERLAY
    paint.colorFilter = PorterDuffColorFilter(randomColor, porterDuffMode)

    val maskPaint = Paint()
    maskPaint. xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_ATOP)

    canvas.drawBitmap(grayScaleImage, 0f, 0f, paint) 

    /**
    Note OVERLAY will disregard the alpha channel and will turn transparent 
    pixels into the randomColor. To resolve this I clip out that transparent area by 
    drawing the image again with the DST_ATOP XferMode
    **/
    canvas.drawBitmap(grayScaleImage, 0f, 0f, maskPaint)

    imageView.setImageBitmap(bitmapCopy)
}

The above result is as follows randomColorGif

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevin CoppockView Question on Stackoverflow
Solution 1 - AndroidRichard LalancetteView Answer on Stackoverflow
Solution 2 - AndroidPabloView Answer on Stackoverflow
Solution 3 - AndroidRonnieView Answer on Stackoverflow
Solution 4 - AndroidJared RummlerView Answer on Stackoverflow
Solution 5 - AndroidjaninView Answer on Stackoverflow
Solution 6 - AndroidmlipmanView Answer on Stackoverflow
Solution 7 - AndroidJadeView Answer on Stackoverflow
Solution 8 - Androiduser1517910View Answer on Stackoverflow
Solution 9 - Androiduser2991535View Answer on Stackoverflow
Solution 10 - AndroidjameseroniousView Answer on Stackoverflow