/******************************************************************************* * Copyright 2012-present Pixate, 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 com.pixate.freestyle.util; import java.util.HashMap; import java.util.Map; import android.content.res.ColorStateList; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.view.View; import com.pixate.freestyle.styling.cache.PXStyleInfo; public class PXColorUtil { // Holds all the possible Android color state names and values. Note that // the keys that are used in this map will omit the "state_" prefix that // android defines in the name of the attribute. private static final Map<String, Integer> STATES; static { STATES = new HashMap<String, Integer>(); STATES.put("focused", android.R.attr.state_focused); STATES.put("window_focused", android.R.attr.state_window_focused); STATES.put("enabled", android.R.attr.state_enabled); STATES.put("checked", android.R.attr.state_checked); STATES.put("checkable", android.R.attr.state_checkable); STATES.put("selected", android.R.attr.state_selected); STATES.put("pressed", android.R.attr.state_pressed); // note that default is "color" STATES.put(PXStyleInfo.DEFAULT_STYLE, android.R.attr.color); } /** * Returns a map of color supported states. These states can be used with * the {@link ColorStateList} to set a text color on a view. * <ul> * <li>"state_focused" * <li>"state_window_focused" * <li>"state_enabled" * <li>"state_checked" * <li>"state_checkable" * <li>"state_selected" * <li>"state_pressed" * <li>"color" (default) * </ul> * * @return A supported states map that */ public static Map<String, Integer> getSupportedStates() { return new HashMap<String, Integer>(STATES); } /** * Returns the integer state value that is mapped to the given state name. * In case none can be mapped, the method returns {@link Integer#MIN_VALUE}. * * @param stateName * @return The color state integer value; {@link Integer#MIN_VALUE} in case * the given state name cannot be matched. */ public static int getStateValue(String stateName) { if (STATES.containsKey(stateName)) { return STATES.get(stateName); } return Integer.MIN_VALUE; } /** * Returns a color from a color-hex string. * * @param hex * @return A color. */ public static int colorFromHexString(String hex) { return colorFromHexString(hex, 1.0F); } /** * Returns a color from a color-hex string and an alpha value. * * @param hex * @param alpha * @return A color. */ public static int colorFromHexString(String hex, float alpha) { if (hex == null || hex.length() == 0) { throw new IllegalArgumentException("Hex color was null or empty"); } int color = 0; if (hex.charAt(0) != '#') { color = Color.parseColor('#' + hex); } else { color = Color.parseColor(hex); } return Color.argb((int) (alpha * 255), Color.red(color), Color.green(color), Color.blue(color)); } /** * Convert HSL to color. * * @param alpha * @param hue * @param saturation * @param lightness * @return */ public static int hslToColor(int alpha, float hue, float saturation, float lightness) { float hh = hue; float ss = saturation; float ll = lightness; float h, s, v; h = hh; ll *= 2; ss *= (ll <= 1) ? ll : 2 - ll; v = (ll + ss) / 2; s = ((ll + ss) != 0) ? (2 * ss) / (ll + ss) : 0; return Color.HSVToColor(alpha, new float[] { h, s, v }); } /** * Convert a color to a HSL array. * * @param color The color to convert. * @param hsl A size-3 array to load with the HSL values. */ public static void colorToHsl(int color, float[] hsl) { float r = ((0x00ff0000 & color) >> 16) / 255.0F; float g = ((0x0000ff00 & color) >> 8) / 255.0F; float b = ((0x000000ff & color)) / 255.0F; float max = Math.max(Math.max(r, g), b); float min = Math.min(Math.min(r, g), b); float c = max - min; float hTemp = 0.0F; if (c == 0) { hTemp = 0; } else if (max == r) { hTemp = (float) (g - b) / c; if (hTemp < 0) hTemp += 6.0F; } else if (max == g) { hTemp = (float) (b - r) / c + 2.0F; } else if (max == b) { hTemp = (float) (r - g) / c + 4.0F; } float h = 60.0F * hTemp; float l = (max + min) * 0.5F; float s; if (c == 0) { s = 0.0F; } else { s = c / (1 - Math.abs(2.0F * l - 1.0F)); } hsl[0] = h; hsl[1] = s; hsl[2] = l; } /** * Creates a color states-list. * * @param color * @return A state-list */ public static ColorStateList createColorStateList(int color) { // @formatter:off // FIXME - This is buggy. The minute we set the color in, the button is no longer clickable.... // [[-16842910], [16842908, -16842910], [16842919], [16842913], [16842908], []] // [-2147483648, -2147483648, -1, -1, -1, -16777216] return new ColorStateList( new int[][] { new int[] { -android.R.attr.state_enabled }, new int[] { android.R.attr.state_focused, -android.R.attr.state_enabled}, new int[] { android.R.attr.state_pressed }, new int[] { android.R.attr.state_selected }, new int[] { android.R.attr.state_focused}, new int[0] }, new int[] { Integer.MIN_VALUE, // !enabled Integer.MIN_VALUE, // focused & !enabled Color.WHITE, // pressed Color.WHITE, // selected Color.WHITE, // focused color }); // @formatter:on } /** * Returns a color that match the given SVG color string.<br> * This is a convenient method for accessing the {@link SVGColors}. You can * also call the {@link SVGColors#get(String)} directly. Also, make sure you * call {@link SVGColors#release()} when there is no need for immediate * mapping of SVG color string to color values. * * @param SVGColorName * @return A color * @see SVGColors#get(String) * @see SVGColors#release() */ public static int getColorFromSVGName(String SVGColorString) { return SVGColors.get(SVGColorString); } /** * Darken a color by percent. * * @param color * @param percent 0.0 - 1.0 * @return A new, darker color. */ public static int darkenByPercent(int color, float percent) { // TODO We may try an HSV approach... // float[] hsv = new float[3]; // Color.colorToHSV(color, hsv); // hsv[2] *= percent; // return Color.HSVToColor(hsv); float r = Color.red(color) * percent; float g = Color.green(color) * percent; float b = Color.blue(color) * percent; int ir = Math.min(255, (int) r); int ig = Math.min(255, (int) g); int ib = Math.min(255, (int) b); int ia = Color.alpha(color); return (Color.argb(ia, ir, ig, ib)); } /** * Lighten a color by percent. * * @param color * @param percent 0.0 - 1.0 * @return A new, lighter color. */ public static int lightterByPercent(int color, float percent) { // TODO We may try an HSV approach... // float[] hsv = new float[3]; // Color.colorToHSV(color, hsv); // hsv[2] *= (1 + percent); // return Color.HSVToColor(hsv); float r = Color.red(color) * (1 + percent); float g = Color.green(color) * (1 + percent); float b = Color.blue(color) * (1 + percent); int ir = Math.min(255, (int) r); int ig = Math.min(255, (int) g); int ib = Math.min(255, (int) b); int ia = Color.alpha(color); return (Color.argb(ia, ir, ig, ib)); } /** * Append an alpha value to the given color. * * @param color * @param alpha a 0.0 to a 1.0 alpha value (will be translated to 0-255) * @return The new color value. */ public static int colorWithAlpha(int color, float alpha) { if (alpha < 0f) { alpha = 0f; } else if (alpha > 1f) { alpha = 1f; } return Color.argb((int) (alpha * 255), Color.red(color), Color.green(color), Color.blue(color)); } /** * Sets the Hue value on a view that has a colored background. In case the * view's background is not a {@link ColorDrawable}, or does not contain one * in a {@link LayerDrawable}, nothing will be applied. * * @param view * @param hue */ public static void setHue(View view, float hue) { ColorDrawable colorDrawable = getColorDrawableBackground(view); if (colorDrawable != null) { int color = colorDrawable.getColor(); float[] hsl = new float[3]; PXColorUtil.colorToHsl(color, hsl); colorDrawable.setColor(PXColorUtil.hslToColor(Color.alpha(color), hue, hsl[1], hsl[2])); } } /** * Sets the Saturation value on a view that has a colored background. In * case the view's background is not a {@link ColorDrawable}, or does not * contain one in a {@link LayerDrawable}, nothing will be applied. * * @param view * @param saturation */ public static void setSaturation(View view, float saturation) { ColorDrawable colorDrawable = getColorDrawableBackground(view); if (colorDrawable != null) { int color = colorDrawable.getColor(); float[] hsl = new float[3]; PXColorUtil.colorToHsl(color, hsl); colorDrawable.setColor(PXColorUtil.hslToColor(Color.alpha(color), hsl[0], saturation, hsl[2])); } } /** * Sets the Brightness value on a view that has a colored background. In * case the view's background is not a {@link ColorDrawable}, or does not * contain one in a {@link LayerDrawable}, nothing will be applied. * * @param view * @param brightness */ public static void setBrightness(View view, float brightness) { ColorDrawable colorDrawable = getColorDrawableBackground(view); if (colorDrawable != null) { int color = colorDrawable.getColor(); float[] hsl = new float[3]; PXColorUtil.colorToHsl(color, hsl); colorDrawable.setColor(PXColorUtil.hslToColor(Color.alpha(color), hsl[0], hsl[1], brightness)); } } /** * Returns the HSL value of a view that has a colored background. In case * the view's background is not a {@link ColorDrawable}, or does not contain * a color-drawable in one of its layers, the return value is * <code>null</code> * * @param view * @return The hue value (<code>null</code> in case the background is not a * {@link ColorDrawable}) */ public static float[] getHSL(View view) { ColorDrawable colorDrawable = getColorDrawableBackground(view); if (colorDrawable != null) { int color = colorDrawable.getColor(); float[] hsl = new float[3]; PXColorUtil.colorToHsl(color, hsl); return hsl; } return null; } /** * Sets the color for a view with a colored background. In case the view's * background is not a {@link ColorDrawable}, or does not contain a * color-drawable in one of its layers, nothing happens. * * @param view * @param color */ public static void setColor(View view, int color) { ColorDrawable colorDrawable = getColorDrawableBackground(view); if (colorDrawable != null) { colorDrawable.setColor(color); } } /** * Returns the background color value for a View that have a * {@link ColorDrawable} background, or a {@link LayerDrawable} background * that contains one. * * @param view * @return The view's background color. -1 in case the view does not have a * ColorDrawable background. */ public static int getColor(View view) { ColorDrawable colorDrawable = getColorDrawableBackground(view); if (colorDrawable != null) { return colorDrawable.getColor(); } return -1; } /** * Returns the View's {@link ColorDrawable} background in case it has one. * The {@link ColorDrawable} may be set directly as the View's background, * or nested within a {@link LayerDrawable}. In case of a * {@link LayerDrawable}, the method will return the first color-drawable it * finds. * * @param view * @return A {@link ColorDrawable}, or <code>null</code> in case not found. */ private static ColorDrawable getColorDrawableBackground(View view) { if (view != null) { Drawable background = view.getBackground(); if (background instanceof ColorDrawable) { return (ColorDrawable) background; } if (background instanceof LayerDrawable) { LayerDrawable layeredBG = (LayerDrawable) background; int numberOfLayers = layeredBG.getNumberOfLayers(); for (int i = 0; i < numberOfLayers; i++) { if (layeredBG.getDrawable(i) instanceof ColorDrawable) { return (ColorDrawable) layeredBG.getDrawable(i); } } } } return null; } }