public class

ImageFilterView

extends AppCompatImageView

 java.lang.Object

↳ImageView

androidx.appcompat.widget.AppCompatImageView

↳androidx.constraintlayout.utils.widget.ImageFilterView

Gradle dependencies

compile group: 'androidx.constraintlayout', name: 'constraintlayout', version: '2.2.0-beta01'

  • groupId: androidx.constraintlayout
  • artifactId: constraintlayout
  • version: 2.2.0-beta01

Artifact androidx.constraintlayout:constraintlayout:2.2.0-beta01 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.constraintlayout:constraintlayout com.android.support.constraint:constraint-layout

Overview

An ImageView that can display, combine and filter images. Added in 2.0

Subclass of ImageView to handle various common filtering operations

ImageFilterView attributes

altSrc Provide and alternative image to the src image to allow cross fading
saturation Sets the saturation of the image.
0 = grayscale, 1 = original, 2 = hyper saturated
brightness Sets the brightness of the image.
0 = black, 1 = original, 2 = twice as bright
warmth This adjust the apparent color temperature of the image.
1=neutral, 2=warm, .5=cold
contrast This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast
crossfade Set the current mix between the two images.
0=src 1= altSrc image
round (id) call the TransitionListener with this trigger id
roundPercent Set the corner radius of curvature as a fraction of the smaller side. For squares 1 will result in a circle
overlay Defines whether the alt image will be faded in on top of the original image or if it will be crossfaded with it. Default is true. Set to false for semitransparent objects

Summary

Constructors
publicImageFilterView(Context context)

publicImageFilterView(Context context, AttributeSet attrs)

publicImageFilterView(Context context, AttributeSet attrs, int defStyleAttr)

Methods
public voiddraw(Canvas canvas)

public floatgetBrightness()

Returns the currently applied brightness

public floatgetContrast()

Returns the currently applied contrast

public floatgetCrossfade()

Returns the currently applied crossfade.

public floatgetImagePanX()

Gets the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

public floatgetImagePanY()

gets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

public floatgetImageRotate()

gets the rotation

public floatgetImageZoom()

gets the zoom where 1 scales the image just enough to fill the view

public floatgetRound()

Get the corner radius of curvature NaN = RoundPercent in effect.

public floatgetRoundPercent()

Get the fractional corner radius of curvature.

public floatgetSaturation()

Returns the currently applied saturation

public floatgetWarmth()

Returns the currently applied warmth

public voidlayout(int l, int t, int r, int b)

public voidsetAltImageDrawable(Drawable altDrawable)

Set the alternative Image Drawable used in cross fading.

public voidsetAltImageResource(int resId)

Set the alternative Image resource used in cross fading

public voidsetBrightness(float brightness)

sets the brightness of the image; 0 = black, 1 = original, 2 = twice as bright

public voidsetContrast(float contrast)

This sets the contrast.

public voidsetCrossfade(float crossfade)

Set the current mix between the two images that can be set on this view.

public voidsetImageDrawable(Drawable drawable)

public voidsetImagePanX(float pan)

sets the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

public voidsetImagePanY(float pan)

sets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

public voidsetImageResource(int resId)

Sets a drawable as the content of this ImageView.

public voidsetImageRotate(float rotation)

sets the rotation angle of the image in degrees

public voidsetImageZoom(float zoom)

sets the zoom where 1 scales the image just enough to fill the view

public voidsetRound(float round)

Set the corner radius of curvature

public voidsetRoundPercent(float round)

Set the corner radius of curvature as a fraction of the smaller side.

public voidsetSaturation(float saturation)

sets the saturation of the image; 0 = grayscale, 1 = original, 2 = hyper saturated

public voidsetWarmth(float warmth)

This makes the apparent color temperature of the image warmer or colder.

from AppCompatImageViewdrawableStateChanged, getSupportBackgroundTintList, getSupportBackgroundTintMode, getSupportImageTintList, getSupportImageTintMode, hasOverlappingRendering, setBackgroundDrawable, setBackgroundResource, setImageBitmap, setImageLevel, setImageURI, setSupportBackgroundTintList, setSupportBackgroundTintMode, setSupportImageTintList, setSupportImageTintMode
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public ImageFilterView(Context context)

public ImageFilterView(Context context, AttributeSet attrs)

public ImageFilterView(Context context, AttributeSet attrs, int defStyleAttr)

Methods

public float getImagePanX()

Gets the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

Returns:

the pan in X. Where 0 is centered = Float. NaN if not set

public float getImagePanY()

gets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

Returns:

pan in y. Where 0 is centered NaN if not set

public float getImageZoom()

gets the zoom where 1 scales the image just enough to fill the view

Returns:

the zoom factor

public float getImageRotate()

gets the rotation

Returns:

the rotation in degrees

public void setImagePanX(float pan)

sets the pan from the center pan of 1 the image is "all the way to the right" if the images width is greater than the screen width, pan = 1 results in the left edge lining up if the images width is less than the screen width, pan = 1 results in the right edges lining up if image width == screen width it does nothing

Parameters:

pan: sets the pan in X. Where 0 is centered

public void setImagePanY(float pan)

sets the pan from the center pan of 1 the image is "all the way to the bottom" if the images width is greater than the screen height, pan = 1 results in the bottom edge lining up if the images width is less than the screen height, pan = 1 results in the top edges lining up if image height == screen height it does nothing

Parameters:

pan: sets the pan in X. Where 0 is centered

public void setImageZoom(float zoom)

sets the zoom where 1 scales the image just enough to fill the view

Parameters:

zoom: the zoom factor

public void setImageRotate(float rotation)

sets the rotation angle of the image in degrees

Parameters:

rotation: the rotation in degrees

public void setImageDrawable(Drawable drawable)

public void setImageResource(int resId)

Sets a drawable as the content of this ImageView.

Allows the use of vector drawables when running on older versions of the platform.

Parameters:

resId: the resource identifier of the drawable

See also: {@link androidx.appcompat.R.attr#srcCompat}

public void setAltImageResource(int resId)

Set the alternative Image resource used in cross fading

Parameters:

resId: id of drawable

public void setAltImageDrawable(Drawable altDrawable)

Set the alternative Image Drawable used in cross fading.

Parameters:

altDrawable: of drawable

public void setSaturation(float saturation)

sets the saturation of the image; 0 = grayscale, 1 = original, 2 = hyper saturated

Parameters:

saturation:

public float getSaturation()

Returns the currently applied saturation

Returns:

0 = grayscale, 1 = original, 2 = hyper saturated

public void setContrast(float contrast)

This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast

Parameters:

contrast:

public float getContrast()

Returns the currently applied contrast

Returns:

1 = unchanged, 0 = gray, 2 = high contrast

public void setWarmth(float warmth)

This makes the apparent color temperature of the image warmer or colder.

Parameters:

warmth: 1 is neutral, 2 is warm, .5 is cold

public float getWarmth()

Returns the currently applied warmth

Returns:

warmth 1 is neutral, 2 is warm, .5 is cold

public void setCrossfade(float crossfade)

Set the current mix between the two images that can be set on this view.

Parameters:

crossfade: a number from 0 to 1

public float getCrossfade()

Returns the currently applied crossfade.

Returns:

a number from 0 to 1

public void setBrightness(float brightness)

sets the brightness of the image; 0 = black, 1 = original, 2 = twice as bright

Parameters:

brightness:

public float getBrightness()

Returns the currently applied brightness

Returns:

brightness 0 = black, 1 = original, 2 = twice as bright

public void setRoundPercent(float round)

Set the corner radius of curvature as a fraction of the smaller side. For squares 1 will result in a circle

Parameters:

round: the radius of curvature as a fraction of the smaller width

public void setRound(float round)

Set the corner radius of curvature

Parameters:

round: the radius of curvature NaN = default meaning roundPercent in effect

public float getRoundPercent()

Get the fractional corner radius of curvature.

Returns:

Fractional radius of curvature with respect to smallest size

public float getRound()

Get the corner radius of curvature NaN = RoundPercent in effect.

Returns:

Radius of curvature

public void draw(Canvas canvas)

public void layout(int l, int t, int r, int b)

Source

/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.constraintlayout.utils.widget;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorMatrix;
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.appcompat.content.res.AppCompatResources;
import androidx.constraintlayout.widget.R;

/**
 * An ImageView that can display, combine and filter images. <b>Added in 2.0</b>
 * <p>
 * Subclass of ImageView to handle various common filtering operations
 * </p>
 *
 * <h2>ImageFilterView attributes</h2>
 * <table summary="KeyTrigger attributes">
 * <tr>
 * <td>altSrc</td>
 * <td>Provide and alternative image to the src image to allow cross fading</td>
 * </tr>
 * <tr>
 * <td>saturation</td>
 * <td>Sets the saturation of the image.<br>  0 = grayscale, 1 = original, 2 = hyper saturated</td>
 * </tr
 * <tr>
 * <td>brightness</td>
 * <td>Sets the brightness of the image.<br>  0 = black, 1 = original, 2 = twice as bright
 * </td>
 * </tr>
 * <tr>
 * <td>warmth</td>
 * <td>This adjust the apparent color temperature of the image.<br> 1=neutral, 2=warm, .5=cold</td>
 * </tr>
 * <tr>
 * <td>contrast</td>
 * <td>This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast</td>
 * </tr>
 * <tr>
 * <td>crossfade</td>
 * <td>Set the current mix between the two images. <br>  0=src 1= altSrc image</td>
 * </tr>
 * <tr>
 * <td>round</td>
 * <td>(id) call the TransitionListener with this trigger id</td>
 * </tr>
 * <tr>
 * <td>roundPercent</td>
 * <td>Set the corner radius of curvature  as a fraction of the smaller side.
 *     For squares 1 will result in a circle</td>
 * </tr>
 * <tr>
 * <td>overlay</td>
 * <td>Defines whether the alt image will be faded in on top of the original image or if it will be
 *     crossfaded with it. Default is true. Set to false for semitransparent objects</td>
 * </tr>
 * </table>
 */
public class ImageFilterView extends androidx.appcompat.widget.AppCompatImageView {
    static class ImageMatrix {
        float[] mMatrix = new float[4 * 5];
        ColorMatrix mColorMatrix = new ColorMatrix();
        ColorMatrix mTmpColorMatrix = new ColorMatrix();
        float mBrightness = 1;
        float mSaturation = 1;
        float mContrast = 1;
        float mWarmth = 1;

        private void saturation(float saturationStrength) {
            float Rf = 0.2999f;
            float Gf = 0.587f;
            float Bf = 0.114f;
            float s = saturationStrength;

            float ms = 1.0f - s;
            float Rt = Rf * ms;
            float Gt = Gf * ms;
            float Bt = Bf * ms;

            mMatrix[0] = (Rt + s);
            mMatrix[1] = Gt;
            mMatrix[2] = Bt;
            mMatrix[3] = 0;
            mMatrix[4] = 0;

            mMatrix[5] = Rt;
            mMatrix[6] = (Gt + s);
            mMatrix[7] = Bt;
            mMatrix[8] = 0;
            mMatrix[9] = 0;

            mMatrix[10] = Rt;
            mMatrix[11] = Gt;
            mMatrix[12] = (Bt + s);
            mMatrix[13] = 0;
            mMatrix[14] = 0;

            mMatrix[15] = 0;
            mMatrix[16] = 0;
            mMatrix[17] = 0;
            mMatrix[18] = 1;
            mMatrix[19] = 0;
        }

        private void warmth(float warmth) {
            float baseTemperature = 5000;
            if (warmth <= 0) warmth = .01f;
            float tmpColor_r;
            float tmpColor_g;
            float tmpColor_b;

            float kelvin = baseTemperature / warmth;
            { // simulate a black body radiation
                float centiKelvin = kelvin / 100;
                float colorR, colorG, colorB;
                if (centiKelvin > 66) {
                    float tmp = centiKelvin - 60.f;
                    // Original statements (all decimal values)
                    // colorR = (329.698727446f * (float) Math.pow(tmp, -0.1332047592f))
                    // colorG = (288.1221695283f * (float) Math.pow(tmp, 0.0755148492f))
                    colorR = (329.69873f * (float) Math.pow(tmp, -0.13320476f));
                    colorG = (288.12216f * (float) Math.pow(tmp, 0.07551485f));
                } else {
                    // Original statements (all decimal values)
                    // colorG = (99.4708025861f * (float) Math.log(centiKelvin) - 161.1195681661f);
                    colorG = (99.4708f * (float) Math.log(centiKelvin) - 161.11957f);
                    colorR = 255;
                }
                if (centiKelvin < 66) {
                    if (centiKelvin > 19) {
                        // Original statements (all decimal values)
                        // 138.5177312231f * (float) Math.log(centiKelvin - 10) - 305.0447927307f);
                        colorB = (138.51773f
                                * (float) Math.log(centiKelvin - 10) - 305.0448f);
                    } else {
                        colorB = 0;
                    }
                } else {
                    colorB = 255;
                }
                tmpColor_r = Math.min(255, Math.max(colorR, 0));
                tmpColor_g = Math.min(255, Math.max(colorG, 0));
                tmpColor_b = Math.min(255, Math.max(colorB, 0));
            }

            float color_r = tmpColor_r;
            float color_g = tmpColor_g;
            float color_b = tmpColor_b;
            kelvin = baseTemperature;
            { // simulate a black body radiation
                float centiKelvin = kelvin / 100;
                float colorR, colorG, colorB;
                if (centiKelvin > 66) {
                    float tmp = centiKelvin - 60.f;
                    // Original statements (all decimal values)
                    //  colorR = (329.698727446f * (float) Math.pow(tmp, -0.1332047592f));
                    //  colorG = (288.1221695283f * (float) Math.pow(tmp, 0.0755148492f));
                    colorR = (329.69873f * (float) Math.pow(tmp, -0.13320476f));
                    colorG = (288.12216f * (float) Math.pow(tmp, 0.07551485f));

                } else {
                    // Original statements (all decimal values)
                    //float of (99.4708025861f * (float) Math.log(centiKelvin) - 161.1195681661f);
                    colorG = (99.4708f * (float) Math.log(centiKelvin) - 161.11957f);
                    colorR = 255;
                }
                if (centiKelvin < 66) {
                    if (centiKelvin > 19) {
                        // Original statements (all decimal values)
                        //float of (138.5177312231 * Math.log(centiKelvin - 10) - 305.0447927307);
                        colorB = (138.51773f * (float) Math.log(centiKelvin - 10) - 305.0448f);
                    } else {
                        colorB = 0;
                    }
                } else {
                    colorB = 255;
                }
                tmpColor_r = Math.min(255, Math.max(colorR, 0));
                tmpColor_g = Math.min(255, Math.max(colorG, 0));
                tmpColor_b = Math.min(255, Math.max(colorB, 0));
            }

            color_r /= tmpColor_r;
            color_g /= tmpColor_g;
            color_b /= tmpColor_b;
            mMatrix[0] = color_r;
            mMatrix[1] = 0;
            mMatrix[2] = 0;
            mMatrix[3] = 0;
            mMatrix[4] = 0;

            mMatrix[5] = 0;
            mMatrix[6] = color_g;
            mMatrix[7] = 0;
            mMatrix[8] = 0;
            mMatrix[9] = 0;

            mMatrix[10] = 0;
            mMatrix[11] = 0;
            mMatrix[12] = color_b;
            mMatrix[13] = 0;
            mMatrix[14] = 0;

            mMatrix[15] = 0;
            mMatrix[16] = 0;
            mMatrix[17] = 0;
            mMatrix[18] = 1;
            mMatrix[19] = 0;
        }

        private void brightness(float brightness) {

            mMatrix[0] = brightness;
            mMatrix[1] = 0;
            mMatrix[2] = 0;
            mMatrix[3] = 0;
            mMatrix[4] = 0;

            mMatrix[5] = 0;
            mMatrix[6] = brightness;
            mMatrix[7] = 0;
            mMatrix[8] = 0;
            mMatrix[9] = 0;

            mMatrix[10] = 0;
            mMatrix[11] = 0;
            mMatrix[12] = brightness;
            mMatrix[13] = 0;
            mMatrix[14] = 0;

            mMatrix[15] = 0;
            mMatrix[16] = 0;
            mMatrix[17] = 0;
            mMatrix[18] = 1;
            mMatrix[19] = 0;
        }

        void updateMatrix(ImageView view) {
            mColorMatrix.reset();
            boolean filter = false;
            if (mSaturation != 1.0f) {
                saturation(mSaturation);
                mColorMatrix.set(mMatrix);
                filter = true;
            }
            if (mContrast != 1.0f) {
                mTmpColorMatrix.setScale(mContrast, mContrast, mContrast, 1);
                mColorMatrix.postConcat(mTmpColorMatrix);
                filter = true;
            }
            if (mWarmth != 1.0f) {
                warmth(mWarmth);
                mTmpColorMatrix.set(mMatrix);
                mColorMatrix.postConcat(mTmpColorMatrix);
                filter = true;
            }
            if (mBrightness != 1.0f) {
                brightness(mBrightness);
                mTmpColorMatrix.set(mMatrix);
                mColorMatrix.postConcat(mTmpColorMatrix);
                filter = true;
            }

            if (filter) {
                view.setColorFilter(new ColorMatrixColorFilter(mColorMatrix));
            } else {
                view.clearColorFilter();
            }
        }
    }

    private ImageMatrix mImageMatrix = new ImageMatrix();
    private boolean mOverlay = true;
    private Drawable mAltDrawable = null;
    private Drawable mDrawable = null;
    private float mCrossfade = 0;
    private float mRoundPercent = 0; // rounds the corners as a percent
    private float mRound = Float.NaN; // rounds the corners in dp if NaN RoundPercent is in effect
    private Path mPath;
    ViewOutlineProvider mViewOutlineProvider;
    RectF mRect;

    Drawable[] mLayers = new Drawable[2];
    LayerDrawable mLayer;

    // ======================== support for pan/zoom/rotate =================
    // defined as 0 = center of screen
    // if with < scree with,  1 is the right edge lines up with screen
    // if width > screen width, 1 is thee left edge lines up
    // -1 works similarly
    // zoom 1 = the image fits such that the view is filed

    float mPanX = Float.NaN;
    float mPanY = Float.NaN;
    float mZoom = Float.NaN;
    float mRotate = Float.NaN;

    /**
     * Gets the pan from the center
     * pan of 1 the image is "all the way to the right"
     * if the images width is greater than the screen width,
     * pan = 1 results in the left edge lining up
     * if the images width is less than the screen width,
     * pan = 1 results in the right edges lining up
     * if image width == screen width it does nothing
     *
     * @return the pan in X. Where 0 is centered = Float. NaN if not set
     */
    public float getImagePanX() {
        return mPanX;
    }

    /**
     * gets the pan from the center
     * pan of 1 the image is "all the way to the bottom"
     * if the images width is greater than the screen height,
     * pan = 1 results in the bottom edge lining up
     * if the images width is less than the screen height,
     * pan = 1 results in the top edges lining up
     * if image height == screen height it does nothing
     *
     * @return pan in y. Where 0 is centered NaN if not set
     */
    public float getImagePanY() {
        return mPanY;
    }

    /**
     * gets the zoom where 1 scales the image just enough to fill the view
     *
     * @return the zoom factor
     */
    public float getImageZoom() {
        return mZoom;
    }

    /**
     * gets the rotation
     *
     * @return the rotation in degrees
     */
    public float getImageRotate() {
        return mRotate;
    }

    /**
     * sets the pan from the center
     * pan of 1 the image is "all the way to the right"
     * if the images width is greater than the screen width,
     * pan = 1 results in the left edge lining up
     * if the images width is less than the screen width,
     * pan = 1 results in the right edges lining up
     * if image width == screen width it does nothing
     *
     * @param pan sets the pan in X. Where 0 is centered
     */
    public void setImagePanX(float pan) {
        mPanX = pan;
        updateViewMatrix();
    }

    /**
     * sets the pan from the center
     * pan of 1 the image is "all the way to the bottom"
     * if the images width is greater than the screen height,
     * pan = 1 results in the bottom edge lining up
     * if the images width is less than the screen height,
     * pan = 1 results in the top edges lining up
     * if image height == screen height it does nothing
     *
     * @param pan sets the pan in X. Where 0 is centered
     */
    public void setImagePanY(float pan) {
        mPanY = pan;
        updateViewMatrix();
    }

    /**
     * sets the zoom where 1 scales the image just enough to fill the view
     *
     * @param zoom the zoom factor
     */
    public void setImageZoom(float zoom) {
        mZoom = zoom;
        updateViewMatrix();
    }

    /**
     * sets the rotation angle of the image in degrees
     *
     * @param rotation the rotation in degrees
     */
    public void setImageRotate(float rotation) {
        mRotate = rotation;
        updateViewMatrix();
    }

    @Override
    public void setImageDrawable(Drawable drawable) {
        if (mAltDrawable != null && drawable != null) {
            mDrawable = drawable.mutate();
            mLayers[0] = mDrawable;
            mLayers[1] = mAltDrawable;
            mLayer = new LayerDrawable(mLayers);
            super.setImageDrawable(mLayer);
            setCrossfade(mCrossfade);
        } else {
            super.setImageDrawable(drawable);
        }
    }

    @Override
    public void setImageResource(int resId) {
        if (mAltDrawable != null) {
            mDrawable = AppCompatResources.getDrawable(getContext(), resId).mutate();
            mLayers[0] = mDrawable;
            mLayers[1] = mAltDrawable;
            mLayer = new LayerDrawable(mLayers);
            super.setImageDrawable(mLayer);
            setCrossfade(mCrossfade);
        } else {
            super.setImageResource(resId);
        }
    }

    /**
     * Set the alternative Image resource used in cross fading
     * @param resId id of drawable
     */
    public void setAltImageResource(int resId) {
        mAltDrawable = AppCompatResources.getDrawable(getContext(), resId);
        setAltImageDrawable(mAltDrawable);
    }

    /**
     * Set the alternative Image Drawable used in cross fading.
     * @param altDrawable of drawable
     */
    public void setAltImageDrawable(Drawable altDrawable) {
        mAltDrawable = altDrawable.mutate();
        mLayers[0] = mDrawable;
        mLayers[1] = mAltDrawable;
        mLayer = new LayerDrawable(mLayers);
        super.setImageDrawable(mLayer);
        setCrossfade(mCrossfade);
    }

    private void updateViewMatrix() {
        if (Float.isNaN(mPanX)
                && Float.isNaN(mPanY)
                && Float.isNaN(mZoom)
                && Float.isNaN(mRotate)
        ) {
            setScaleType(ScaleType.FIT_CENTER);
            return;
        }
        setMatrix();
    }

    private void setMatrix() {
        if (Float.isNaN(mPanX)
                && Float.isNaN(mPanY)
                && Float.isNaN(mZoom)
                && Float.isNaN(mRotate)
        ) {
            return;
        }
        float panX = Float.isNaN(mPanX) ? 0 : mPanX;
        float panY = Float.isNaN(mPanY) ? 0 : mPanY;
        float zoom = Float.isNaN(mZoom) ? 1 : mZoom;
        float rota = Float.isNaN(mRotate) ? 0 : mRotate;
        Matrix imageMatrix = new Matrix();
        imageMatrix.reset();
        float iw = getDrawable().getIntrinsicWidth();
        float ih = getDrawable().getIntrinsicHeight();
        float sw = getWidth();
        float sh = getHeight();
        float scale = zoom * ((iw * sh < ih * sw) ? sw / iw : sh / ih);
        imageMatrix.postScale(scale, scale);
        float tx = 0.5f * (panX * (sw - scale * iw) + sw - (scale * iw));
        float ty = 0.5f * (panY * (sh - scale * ih) + sh - (scale * ih));
        imageMatrix.postTranslate(tx, ty);
        imageMatrix.postRotate(rota, sw / 2, sh / 2);
        setImageMatrix(imageMatrix);
        setScaleType(ScaleType.MATRIX);
    }

    public ImageFilterView(Context context) {
        super(context);
        init(context, null);
    }

    public ImageFilterView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    public ImageFilterView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        if (attrs != null) {
            TypedArray a = context
                    .obtainStyledAttributes(attrs, R.styleable.ImageFilterView);
            final int count = a.getIndexCount();
            mAltDrawable = a.getDrawable(R.styleable.ImageFilterView_altSrc);

            for (int i = 0; i < count; i++) {
                int attr = a.getIndex(i);
                if (attr == R.styleable.ImageFilterView_crossfade) {
                    mCrossfade = a.getFloat(attr, 0);
                } else if (attr == R.styleable.ImageFilterView_warmth) {
                    setWarmth(a.getFloat(attr, 0));
                } else if (attr == R.styleable.ImageFilterView_saturation) {
                    setSaturation(a.getFloat(attr, 0));
                } else if (attr == R.styleable.ImageFilterView_contrast) {
                    setContrast(a.getFloat(attr, 0));
                } else if (attr == R.styleable.ImageFilterView_brightness) {
                    setBrightness(a.getFloat(attr, 0));
                } else if (attr == R.styleable.ImageFilterView_round) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        setRound(a.getDimension(attr, 0));
                    }
                } else if (attr == R.styleable.ImageFilterView_roundPercent) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                        setRoundPercent(a.getFloat(attr, 0));
                    }
                } else if (attr == R.styleable.ImageFilterView_overlay) {
                    setOverlay(a.getBoolean(attr, mOverlay));
                } else if (attr == R.styleable.ImageFilterView_imagePanX) {
                    setImagePanX(a.getFloat(attr, mPanX));
                } else if (attr == R.styleable.ImageFilterView_imagePanY) {
                    setImagePanY(a.getFloat(attr, mPanY));
                } else if (attr == R.styleable.ImageFilterView_imageRotate) {
                    setImageRotate(a.getFloat(attr, mRotate));
                } else if (attr == R.styleable.ImageFilterView_imageZoom) {
                    setImageZoom(a.getFloat(attr, mZoom));
                }
            }
            a.recycle();

            mDrawable = getDrawable();
            if (mAltDrawable != null && mDrawable != null) {

                mLayers[0] = mDrawable = getDrawable().mutate();
                mLayers[1] = mAltDrawable.mutate();

                mLayer = new LayerDrawable(mLayers);
                mLayer.getDrawable(1).setAlpha((int) (255 * mCrossfade));
                if (!mOverlay) {
                    mLayer.getDrawable(0).setAlpha((int) (255 * (1 - mCrossfade)));
                }
                super.setImageDrawable(mLayer);
            } else {
                mDrawable = getDrawable();
                if (mDrawable != null) {
                    mLayers[0] = mDrawable = mDrawable.mutate();
                }
            }
        }
    }

    /**
     * Defines whether the alt image will be faded in on top
     * of the original image or if it will be crossfaded with it.
     * Default is true;
     *
     * @param overlay
     */
    private void setOverlay(boolean overlay) {
        mOverlay = overlay;
    }

    /**
     * sets the saturation of the image;
     * 0 = grayscale, 1 = original, 2 = hyper saturated
     *
     * @param saturation
     */

    public void setSaturation(float saturation) {
        mImageMatrix.mSaturation = saturation;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Returns the currently applied saturation
     *
     * @return 0 = grayscale, 1 = original, 2 = hyper saturated
     */
    public float getSaturation() {
        return mImageMatrix.mSaturation;
    }

    /**
     * This sets the contrast. 1 = unchanged, 0 = gray, 2 = high contrast
     *
     * @param contrast
     */
    public void setContrast(float contrast) {
        mImageMatrix.mContrast = contrast;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Returns the currently applied contrast
     *
     * @return 1 = unchanged, 0 = gray, 2 = high contrast
     */
    public float getContrast() {
        return mImageMatrix.mContrast;
    }

    /**
     * This makes the apparent color temperature of the image warmer or colder.
     *
     * @param warmth 1 is neutral, 2 is warm, .5 is cold
     */
    public void setWarmth(float warmth) {
        mImageMatrix.mWarmth = warmth;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Returns the currently applied warmth
     *
     * @return warmth 1 is neutral, 2 is warm, .5 is cold
     */
    public float getWarmth() {
        return mImageMatrix.mWarmth;
    }

    /**
     * Set the current mix between the two images that can be set on this view.
     *
     * @param crossfade a number from 0 to 1
     */
    public void setCrossfade(float crossfade) {
        mCrossfade = crossfade;
        if (mLayers != null) {
            if (!mOverlay) {
                mLayer.getDrawable(0).setAlpha((int) (255 * (1 - mCrossfade)));
            }
            mLayer.getDrawable(1).setAlpha((int) (255 * mCrossfade));
            super.setImageDrawable(mLayer);
        }
    }

    /**
     * Returns the currently applied crossfade.
     *
     * @return a number from 0 to 1
     */
    public float getCrossfade() {
        return mCrossfade;
    }

    /**
     * sets the brightness of the image;
     * 0 = black, 1 = original, 2 = twice as bright
     *
     * @param brightness
     */

    public void setBrightness(float brightness) {
        mImageMatrix.mBrightness = brightness;
        mImageMatrix.updateMatrix(this);
    }

    /**
     * Returns the currently applied brightness
     *
     * @return brightness 0 = black, 1 = original, 2 = twice as bright
     */
    public float getBrightness() {
        return mImageMatrix.mBrightness;
    }

    /**
     * Set the corner radius of curvature  as a fraction of the smaller side.
     * For squares 1 will result in a circle
     *
     * @param round the radius of curvature as a fraction of the smaller width
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    public void setRoundPercent(float round) {
        boolean change = (mRoundPercent != round);
        mRoundPercent = round;
        if (mRoundPercent != 0.0f) {
            if (mPath == null) {
                mPath = new Path();
            }
            if (mRect == null) {
                mRect = new RectF();
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                if (mViewOutlineProvider == null) {
                    mViewOutlineProvider = new ViewOutlineProvider() {
                        @Override
                        public void getOutline(View view, Outline outline) {
                            int w = getWidth();
                            int h = getHeight();
                            float r = Math.min(w, h) * mRoundPercent / 2;
                            outline.setRoundRect(0, 0, w, h, r);
                        }
                    };
                    setOutlineProvider(mViewOutlineProvider);
                }
                setClipToOutline(true);

            }
            int w = getWidth();
            int h = getHeight();
            float r = Math.min(w, h) * mRoundPercent / 2;
            mRect.set(0, 0, w, h);
            mPath.reset();
            mPath.addRoundRect(mRect, r, r, Path.Direction.CW);
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                setClipToOutline(false);
            }
        }
        if (change) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                invalidateOutline();
            }
        }

    }

    /**
     * Set the corner radius of curvature
     *
     * @param round the radius of curvature  NaN = default meaning roundPercent in effect
     */
    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
    public void setRound(float round) {
        if (Float.isNaN(round)) {
            mRound = round;
            float tmp = mRoundPercent;
            mRoundPercent = -1;
            setRoundPercent(tmp); // force eval of roundPercent
            return;
        }
        boolean change = (mRound != round);
        mRound = round;

        if (mRound != 0.0f) {
            if (mPath == null) {
                mPath = new Path();
            }
            if (mRect == null) {
                mRect = new RectF();
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                if (mViewOutlineProvider == null) {
                    mViewOutlineProvider = new ViewOutlineProvider() {
                        @Override
                        public void getOutline(View view, Outline outline) {
                            int w = getWidth();
                            int h = getHeight();
                            outline.setRoundRect(0, 0, w, h, mRound);
                        }
                    };
                    setOutlineProvider(mViewOutlineProvider);
                }
                setClipToOutline(true);
            }
            int w = getWidth();
            int h = getHeight();
            mRect.set(0, 0, w, h);
            mPath.reset();
            mPath.addRoundRect(mRect, mRound, mRound, Path.Direction.CW);
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                setClipToOutline(false);
            }
        }
        if (change) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
                invalidateOutline();
            }
        }

    }

    /**
     * Get the fractional corner radius of curvature.
     *
     * @return Fractional radius of curvature with respect to smallest size
     */
    public float getRoundPercent() {
        return mRoundPercent;
    }

    /**
     * Get the corner radius of curvature NaN = RoundPercent in effect.
     *
     * @return Radius of curvature
     */
    public float getRound() {
        return mRound;
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        boolean clip = false;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            if (mRoundPercent != 0.0f && mPath != null) {
                clip = true;
                canvas.save();
                canvas.clipPath(mPath);
            }
        }
        super.draw(canvas);
        if (clip) {
            canvas.restore();
        }
    }

    @Override
    public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r, b);
        setMatrix();
    }
}