Stackoverflow has a bunch of examples of how to apply a ColorFilter (and therefore a ColorMatrix) to a bitmap, or an ImageView (which has a built-in API), but applying color effects to an entire View are not nearly as well documented.

The basic idea is to use your ColorMatrix instances in ColorFilters, then wrap that in a ColorMatrixColorFilter. Then apply that to a Paint instance. That’s all pretty well documented, but what happens after that is less so.

One approach is to use dispatchDraw or onDraw:

canvas.saveLayer(null, mPaint, Canvas.ALL_SAVE_FLAG);
super.dispatchDraw(canvas); // or super.onDraw(canvas);
canvas.restore();

But in looking at the docs, we can see several warnings about performance around this approach – in fact more than doubling the work: https://github.com/aosp-mirror/platform_frameworks_base/blob/master/graphics/java/android/graphics/Canvas.java#L413

The short answer is to apply the paint via `setLayerType` – the source recommends a hardware layer but IME either will work:

myView.setLayerType(LAYER_TYPE_HARDWARE, mPaint);

Here’s an example of subclass of Paint that can be inverted, and accepts a range of contrast values. You can share this paint among any number of Views using `setLayerType`. Caveat: invalidating does not seem to update the View as normal, and in some cases I’ve had to call `setLayerType` again after changes to the Paint’s values.


import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;

/**
 * Created by michaeldunn on 2/13/18.
 */

public class ContrastAndInversionPaint extends Paint {

  private float mContrast;
  private boolean mInverted;

  private ColorMatrix mCombinedMatrix = new ColorMatrix();
  private ColorMatrix mContrastMatrix = new ColorMatrix();
  private ColorMatrix mInversionMatrix = 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 float[] getContrastArray() {
    float translate = (-0.5f * mContrast + 0.5f) * 255f;  // 0 contrast would be 127.5, 2 contrast would be -127.5 https://jsfiddle.net/moagrius/r5vo69k9/7/
    return new float[] {
        mContrast, 0, 0, 0, translate,
        0, mContrast, 0, 0, translate,
        0, 0, mContrast, 0, translate,
        0, 0, 0, 1, 0};
  }

  public float getContrast() {
    return mContrast;
  }

  public void setContrast(float contrast) {
    mContrast = contrast;
    updateColorFilters();
  }

  public boolean isInverted() {
    return mInverted;
  }

  public void setInverted(boolean inverted) {
    mInverted = inverted;
    updateColorFilters();
  }

  public void updateColorFilters() {
    // don't reset the inversion matrix because it's only defined once - there's no scaling - it is, or is not, inverted
    mContrastMatrix.reset();
    mCombinedMatrix.reset();
    if (mContrast != 0) {
      mContrastMatrix.set(getContrastArray());
      mCombinedMatrix.postConcat(mContrastMatrix);
    }
    if (mInverted) {
      mCombinedMatrix.postConcat(mInversionMatrix);
    }
    setColorFilter(new ColorMatrixColorFilter(mCombinedMatrix));
  }

}