Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d4610749 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Color extraction shareable library"

parents c926a5ee 65f56581
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
# Copyright (C) 2017 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.

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_USE_AAPT2 := true
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_ANDROID_LIBRARIES := android-support-annotations \
     android-support-v7-palette \
     android-support-v4
LOCAL_MODULE := colorextraction

include $(BUILD_STATIC_JAVA_LIBRARY)
+21 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>

<!--
  ~ Copyright (C) 2017 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.
  -->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.google.android.colorextraction">
</manifest>
+170 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 com.google.android.colorextraction;

import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
import android.graphics.Color;
import android.util.Log;

import com.google.android.colorextraction.types.ExtractionType;
import com.google.android.colorextraction.types.Tonal;

/**
 * Class to process wallpaper colors and generate a tonal palette based on them.
 */
public class ColorExtractor implements WallpaperManager.OnColorsChangedListener {
    private static final String TAG = "ColorExtractor";
    private static final int FALLBACK_COLOR = Color.BLACK;

    private int mMainFallbackColor = FALLBACK_COLOR;
    private int mSecondaryFallbackColor = FALLBACK_COLOR;
    private final GradientColors mSystemColors;
    private final GradientColors mLockColors;
    private final Context mContext;
    private final ExtractionType mExtractionType;
    private OnColorsChangedListener mListener;

    public ColorExtractor(Context context) {
        mContext = context;
        mSystemColors = new GradientColors();
        mLockColors = new GradientColors();
        mExtractionType = new Tonal();

        WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class);

        if (wallpaperManager == null) {
            Log.w(TAG, "Can't listen to color changes!");
        } else {
            wallpaperManager.addOnColorsChangedListener(this);
            extractInto(wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_SYSTEM),
                    mSystemColors);
            extractInto(wallpaperManager.getWallpaperColors(WallpaperManager.FLAG_LOCK),
                    mLockColors);
        }
    }

    public GradientColors getColors(int which) {
        if (which == WallpaperManager.FLAG_LOCK) {
            return mLockColors;
        } else if (which == WallpaperManager.FLAG_SYSTEM) {
            return mSystemColors;
        } else {
            throw new IllegalArgumentException("which should be either FLAG_SYSTEM or FLAG_LOCK");
        }
    }

    public void setListener(OnColorsChangedListener listener) {
        mListener = listener;
    }

    @Override
    public void onColorsChanged(WallpaperColors colors, int which) {
        if ((which & WallpaperManager.FLAG_LOCK) != 0) {
            extractInto(colors, mLockColors);
            if (mListener != null) {
                mListener.onColorsChanged(mLockColors, WallpaperManager.FLAG_LOCK);
            }
        }
        if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
            extractInto(colors, mSystemColors);
            if (mListener != null) {
                mListener.onColorsChanged(mSystemColors, WallpaperManager.FLAG_SYSTEM);
            }
        }
    }

    private void extractInto(WallpaperColors inWallpaperColors, GradientColors outGradientColors) {
        applyFallback(outGradientColors);
        if (inWallpaperColors == null) {
            return;
        }
        mExtractionType.extractInto(inWallpaperColors, outGradientColors);
    }

    private void applyFallback(GradientColors outGradientColors) {
        outGradientColors.setMainColor(mMainFallbackColor);
        outGradientColors.setSecondaryColor(mSecondaryFallbackColor);
    }

    public void destroy() {
        WallpaperManager wallpaperManager = mContext.getSystemService(WallpaperManager.class);
        if (wallpaperManager != null) {
            wallpaperManager.removeOnColorsChangedListener(this);
        }
    }

    public static class GradientColors {
        private int mMainColor = FALLBACK_COLOR;
        private int mSecondaryColor = FALLBACK_COLOR;
        private boolean mSupportsDarkText;

        public void setMainColor(int mainColor) {
            mMainColor = mainColor;
        }

        public void setSecondaryColor(int secondaryColor) {
            mSecondaryColor = secondaryColor;
        }

        public void setSupportsDarkText(boolean supportsDarkText) {
            mSupportsDarkText = supportsDarkText;
        }

        public void set(GradientColors other) {
            mMainColor = other.mMainColor;
            mSecondaryColor = other.mSecondaryColor;
            mSupportsDarkText = other.mSupportsDarkText;
        }

        public int getMainColor() {
            return mMainColor;
        }

        public int getSecondaryColor() {
            return mSecondaryColor;
        }

        public boolean supportsDarkText() {
            return mSupportsDarkText;
        }

        @Override
        public boolean equals(Object o) {
            if (o == null || o.getClass() != getClass()) {
                return false;
            }
            GradientColors other = (GradientColors) o;
            return other.mMainColor == mMainColor &&
                    other.mSecondaryColor == mSecondaryColor &&
                    other.mSupportsDarkText == mSupportsDarkText;
        }

        @Override
        public int hashCode() {
            int code = mMainColor;
            code = 31 * code + mSecondaryColor;
            code = 31 * code + (mSupportsDarkText ? 0 : 1);
            return code;
        }
    }

    public interface OnColorsChangedListener {
        void onColorsChanged(GradientColors colors, int which);
    }
}
+206 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 com.google.android.colorextraction.drawable;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.RadialGradient;
import android.graphics.Rect;
import android.graphics.Shader;
import android.graphics.Xfermode;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.graphics.ColorUtils;
import android.view.animation.DecelerateInterpolator;

import com.google.android.colorextraction.ColorExtractor;

/**
 * Draws a gradient based on a Palette
 */
public class GradientDrawable extends Drawable {
    private static final String TAG = "GradientDrawable";

    private static final float CENTRALIZED_CIRCLE_1 = -2;
    private static final int GRADIENT_RADIUS = 480; // in dp
    private static final long COLOR_ANIMATION_DURATION = 2000;

    private int mAlpha = 255;

    private float mDensity;
    private final Paint mPaint;
    private final Rect mWindowBounds;
    private final Splat mSplat;

    private int mMainColor;
    private int mSecondaryColor;
    private ValueAnimator mColorAnimation;

    public GradientDrawable(@NonNull Context context) {
        mDensity = context.getResources().getDisplayMetrics().density;
        mSplat = new Splat(0.50f, 1.00f, GRADIENT_RADIUS, CENTRALIZED_CIRCLE_1);
        mWindowBounds = new Rect();

        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL);
    }

    public void setColors(@NonNull ColorExtractor.GradientColors colors) {
        setColors(colors.getMainColor(), colors.getSecondaryColor(), true);
    }

    public void setColors(@NonNull ColorExtractor.GradientColors colors, boolean animated) {
        setColors(colors.getMainColor(), colors.getSecondaryColor(), animated);
    }

    public void setColors(int mainColor, int secondaryColor, boolean animated) {
        if (mainColor == mMainColor && secondaryColor == mSecondaryColor) {
            return;
        }

        if (mColorAnimation != null && mColorAnimation.isRunning()) {
            mColorAnimation.cancel();
        }

        if (animated) {
            final int mainFrom = mMainColor;
            final int secFrom = mSecondaryColor;

            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
            anim.setDuration(COLOR_ANIMATION_DURATION);
            anim.addUpdateListener(animation -> {
                float ratio = (float) animation.getAnimatedValue();
                mMainColor = ColorUtils.blendARGB(mainFrom, mainColor, ratio);
                mSecondaryColor = ColorUtils.blendARGB(secFrom, secondaryColor, ratio);
                buildPaints();
                invalidateSelf();
            });
            anim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation, boolean isReverse) {
                    if (mColorAnimation == animation) {
                        mColorAnimation = null;
                    }
                }
            });
            anim.setInterpolator(new DecelerateInterpolator());
            anim.start();
            mColorAnimation = anim;
        } else {
            mMainColor = mainColor;
            mSecondaryColor = secondaryColor;
            buildPaints();
            invalidateSelf();
        }
    }

    @Override
    public void setAlpha(int alpha) {
        if (alpha != mAlpha) {
            mAlpha = alpha;
            mPaint.setAlpha(mAlpha);
            invalidateSelf();
        }
    }

    @Override
    public int getAlpha() {
        return mAlpha;
    }

    @Override
    public void setXfermode(@Nullable Xfermode mode) {
        mPaint.setXfermode(mode);
        invalidateSelf();
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
    }

    @Override
    public ColorFilter getColorFilter() {
        return mPaint.getColorFilter();
    }

    @Override
    public int getOpacity() {
        return mAlpha == 255 ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
    }

    public void setScreenSize(int width, int height) {
        mWindowBounds.set(0, 0, width, height);
        setBounds(0, 0, width, height);
        buildPaints();
    }

    private void buildPaints() {
        final Rect bounds = mWindowBounds;

        float w = bounds.width();
        float h = bounds.height();

        float x = mSplat.x * w;
        float y = mSplat.y * h;

        float radius = mSplat.radius * mDensity;

        // When we have only a single alpha gradient, we increase quality
        // (avoiding banding) by merging the background solid color into
        // the gradient directly
        RadialGradient radialGradient = new RadialGradient(x, y, radius,
                mMainColor, mSecondaryColor, Shader.TileMode.CLAMP);
        mPaint.setShader(radialGradient);
    }

    @Override
    public void draw(@NonNull Canvas canvas) {
        final Rect bounds = mWindowBounds;

        // Splat each gradient
        float w = bounds.width();
        float h = bounds.height();

        float x = mSplat.x * w;
        float y = mSplat.y * h;

        float radius = Math.max(w, h);
        canvas.drawRect(x - radius, y - radius, x + radius, y + radius, mPaint);
    }

    static final class Splat {
        final float x;
        final float y;
        final float radius;
        final float colorIndex;

        Splat(float x, float y, float radius, float colorIndex) {
            this.x = x;
            this.y = y;
            this.radius = radius;
            this.colorIndex = colorIndex;
        }
    }
}
 No newline at end of file
+37 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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 com.google.android.colorextraction.types;

import android.app.WallpaperColors;

import com.google.android.colorextraction.ColorExtractor;

/**
 * Interface to allow various color extraction implementations.
 */
public interface ExtractionType {

    /**
     * Executes color extraction by reading WallpaperColors and setting
     * main and secondary colors on GradientColors.
     *
     * @param inWallpaperColors where to read from
     * @param outGradientColors object that should receive the colors
     */
    void extractInto(WallpaperColors inWallpaperColors,
            ColorExtractor.GradientColors outGradientColors);
}
Loading