/* * Copyright 2015 Google Inc. * * 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 io.plaidapp.util; import android.graphics.Bitmap; import android.support.annotation.CheckResult; import android.support.annotation.ColorInt; import android.support.annotation.FloatRange; import android.support.annotation.IntDef; import android.support.annotation.IntRange; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v7.graphics.Palette; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Utility methods for working with colors. */ public class ColorUtils { private ColorUtils() { } public static final int IS_LIGHT = 0; public static final int IS_DARK = 1; public static final int LIGHTNESS_UNKNOWN = 2; /** * Set the alpha component of {@code color} to be {@code alpha}. */ public static @CheckResult @ColorInt int modifyAlpha(@ColorInt int color, @IntRange(from = 0, to = 255) int alpha) { return (color & 0x00ffffff) | (alpha << 24); } /** * Set the alpha component of {@code color} to be {@code alpha}. */ public static @CheckResult @ColorInt int modifyAlpha(@ColorInt int color, @FloatRange(from = 0f, to = 1f) float alpha) { return modifyAlpha(color, (int) (255f * alpha)); } /** * Checks if the most populous color in the given palette is dark * <p/> * Annoyingly we have to return this Lightness 'enum' rather than a boolean as palette isn't * guaranteed to find the most populous color. */ public static @Lightness int isDark(Palette palette) { Palette.Swatch mostPopulous = getMostPopulousSwatch(palette); if (mostPopulous == null) return LIGHTNESS_UNKNOWN; return isDark(mostPopulous.getHsl()) ? IS_DARK : IS_LIGHT; } public static @Nullable Palette.Swatch getMostPopulousSwatch(Palette palette) { Palette.Swatch mostPopulous = null; if (palette != null) { for (Palette.Swatch swatch : palette.getSwatches()) { if (mostPopulous == null || swatch.getPopulation() > mostPopulous.getPopulation()) { mostPopulous = swatch; } } } return mostPopulous; } /** * Determines if a given bitmap is dark. This extracts a palette inline so should not be called * with a large image!! * <p/> * Note: If palette fails then check the color of the central pixel */ public static boolean isDark(@NonNull Bitmap bitmap) { return isDark(bitmap, bitmap.getWidth() / 2, bitmap.getHeight() / 2); } /** * Determines if a given bitmap is dark. This extracts a palette inline so should not be called * with a large image!! If palette fails then check the color of the specified pixel */ public static boolean isDark(@NonNull Bitmap bitmap, int backupPixelX, int backupPixelY) { // first try palette with a small color quant size Palette palette = Palette.from(bitmap).maximumColorCount(3).generate(); if (palette != null && palette.getSwatches().size() > 0) { return isDark(palette) == IS_DARK; } else { // if palette failed, then check the color of the specified pixel return isDark(bitmap.getPixel(backupPixelX, backupPixelY)); } } /** * Check that the lightness value (0–1) */ public static boolean isDark(float[] hsl) { // @Size(3) return hsl[2] < 0.5f; } /** * Convert to HSL & check that the lightness value */ public static boolean isDark(@ColorInt int color) { float[] hsl = new float[3]; android.support.v4.graphics.ColorUtils.colorToHSL(color, hsl); return isDark(hsl); } /** * Calculate a variant of the color to make it more suitable for overlaying information. Light * colors will be lightened and dark colors will be darkened * * @param color the color to adjust * @param isDark whether {@code color} is light or dark * @param lightnessMultiplier the amount to modify the color e.g. 0.1f will alter it by 10% * @return the adjusted color */ public static @ColorInt int scrimify(@ColorInt int color, boolean isDark, @FloatRange(from = 0f, to = 1f) float lightnessMultiplier) { float[] hsl = new float[3]; android.support.v4.graphics.ColorUtils.colorToHSL(color, hsl); if (!isDark) { lightnessMultiplier += 1f; } else { lightnessMultiplier = 1f - lightnessMultiplier; } hsl[2] = MathUtils.constrain(0f, 1f, hsl[2] * lightnessMultiplier); return android.support.v4.graphics.ColorUtils.HSLToColor(hsl); } public static @ColorInt int scrimify(@ColorInt int color, @FloatRange(from = 0f, to = 1f) float lightnessMultiplier) { return scrimify(color, isDark(color), lightnessMultiplier); } @Retention(RetentionPolicy.SOURCE) @IntDef({IS_LIGHT, IS_DARK, LIGHTNESS_UNKNOWN}) public @interface Lightness { } }