Skip to content

Instantly share code, notes, and snippets.

@NikolaDespotoski
Last active August 16, 2016 12:44
Show Gist options
  • Save NikolaDespotoski/0c7ab7dba4f795891a26 to your computer and use it in GitHub Desktop.
Save NikolaDespotoski/0c7ab7dba4f795891a26 to your computer and use it in GitHub Desktop.
Parcelable Palette
/*
* Copyright 2014 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.
*/
import android.graphics.Color;
final class ColorUtils {
private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
private static final int MIN_ALPHA_SEARCH_PRECISION = 10;
private ColorUtils() {}
/**
* Composite two potentially translucent colors over each other and returns the result.
*/
private static int compositeColors(int fg, int bg) {
final float alpha1 = Color.alpha(fg) / 255f;
final float alpha2 = Color.alpha(bg) / 255f;
float a = (alpha1 + alpha2) * (1f - alpha1);
float r = (Color.red(fg) * alpha1) + (Color.red(bg) * alpha2 * (1f - alpha1));
float g = (Color.green(fg) * alpha1) + (Color.green(bg) * alpha2 * (1f - alpha1));
float b = (Color.blue(fg) * alpha1) + (Color.blue(bg) * alpha2 * (1f - alpha1));
return Color.argb((int) a, (int) r, (int) g, (int) b);
}
/**
* Returns the luminance of a color.
*
* Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef
*/
private static double calculateLuminance(int color) {
double red = Color.red(color) / 255d;
red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
double green = Color.green(color) / 255d;
green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4);
double blue = Color.blue(color) / 255d;
blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4);
return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
}
/**
* Returns the contrast ratio between two colors.
*
* Formula defined here: http://www.w3.org/TR/2008/REC-WCAG20-20081211/#contrast-ratiodef
*/
private static double calculateContrast(int foreground, int background) {
if (Color.alpha(background) != 255) {
throw new IllegalArgumentException("background can not be translucent");
}
if (Color.alpha(foreground) < 255) {
// If the foreground is translucent, composite the foreground over the background
foreground = compositeColors(foreground, background);
}
final double luminance1 = calculateLuminance(foreground) + 0.05;
final double luminance2 = calculateLuminance(background) + 0.05;
// Now return the lighter luminance divided by the darker luminance
return Math.max(luminance1, luminance2) / Math.min(luminance1, luminance2);
}
/**
* Finds the minimum alpha value which can be applied to {@code foreground} so that is has a
* contrast value of at least {@code minContrastRatio} when compared to background.
*
* @return the alpha value in the range 0-255.
*/
private static int findMinimumAlpha(int foreground, int background, double minContrastRatio) {
if (Color.alpha(background) != 255) {
throw new IllegalArgumentException("background can not be translucent");
}
// First lets check that a fully opaque foreground has sufficient contrast
int testForeground = modifyAlpha(foreground, 255);
double testRatio = calculateContrast(testForeground, background);
if (testRatio < minContrastRatio) {
// Fully opaque foreground does not have sufficient contrast, return error
return -1;
}
// Binary search to find a value with the minimum value which provides sufficient contrast
int numIterations = 0;
int minAlpha = 0;
int maxAlpha = 255;
while (numIterations <= MIN_ALPHA_SEARCH_MAX_ITERATIONS &&
(maxAlpha - minAlpha) > MIN_ALPHA_SEARCH_PRECISION) {
final int testAlpha = (minAlpha + maxAlpha) / 2;
testForeground = modifyAlpha(foreground, testAlpha);
testRatio = calculateContrast(testForeground, background);
if (testRatio < minContrastRatio) {
minAlpha = testAlpha;
} else {
maxAlpha = testAlpha;
}
numIterations++;
}
// Conservatively return the max of the range of possible alphas, which is known to pass.
return maxAlpha;
}
static int getTextColorForBackground(int backgroundColor, float minContrastRatio) {
// First we will check white as most colors will be dark
final int whiteMinAlpha = ColorUtils
.findMinimumAlpha(Color.WHITE, backgroundColor, minContrastRatio);
if (whiteMinAlpha >= 0) {
return ColorUtils.modifyAlpha(Color.WHITE, whiteMinAlpha);
}
// If we hit here then there is not an translucent white which provides enough contrast,
// so check black
final int blackMinAlpha = ColorUtils
.findMinimumAlpha(Color.BLACK, backgroundColor, minContrastRatio);
if (blackMinAlpha >= 0) {
return ColorUtils.modifyAlpha(Color.BLACK, blackMinAlpha);
}
// This should not happen!
return -1;
}
static void RGBtoHSL(int r, int g, int b, float[] hsl) {
final float rf = r / 255f;
final float gf = g / 255f;
final float bf = b / 255f;
final float max = Math.max(rf, Math.max(gf, bf));
final float min = Math.min(rf, Math.min(gf, bf));
final float deltaMaxMin = max - min;
float h, s;
float l = (max + min) / 2f;
if (max == min) {
// Monochromatic
h = s = 0f;
} else {
if (max == rf) {
h = ((gf - bf) / deltaMaxMin) % 6f;
} else if (max == gf) {
h = ((bf - rf) / deltaMaxMin) + 2f;
} else {
h = ((rf - gf) / deltaMaxMin) + 4f;
}
s = deltaMaxMin / (1f - Math.abs(2f * l - 1f));
}
hsl[0] = (h * 60f) % 360f;
hsl[1] = s;
hsl[2] = l;
}
static int HSLtoRGB (float[] hsl) {
final float h = hsl[0];
final float s = hsl[1];
final float l = hsl[2];
final float c = (1f - Math.abs(2 * l - 1f)) * s;
final float m = l - 0.5f * c;
final float x = c * (1f - Math.abs((h / 60f % 2f) - 1f));
final int hueSegment = (int) h / 60;
int r = 0, g = 0, b = 0;
switch (hueSegment) {
case 0:
r = Math.round(255 * (c + m));
g = Math.round(255 * (x + m));
b = Math.round(255 * m);
break;
case 1:
r = Math.round(255 * (x + m));
g = Math.round(255 * (c + m));
b = Math.round(255 * m);
break;
case 2:
r = Math.round(255 * m);
g = Math.round(255 * (c + m));
b = Math.round(255 * (x + m));
break;
case 3:
r = Math.round(255 * m);
g = Math.round(255 * (x + m));
b = Math.round(255 * (c + m));
break;
case 4:
r = Math.round(255 * (x + m));
g = Math.round(255 * m);
b = Math.round(255 * (c + m));
break;
case 5:
case 6:
r = Math.round(255 * (c + m));
g = Math.round(255 * m);
b = Math.round(255 * (x + m));
break;
}
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
return Color.rgb(r, g, b);
}
/**
* Set the alpha component of {@code color} to be {@code alpha}.
*/
static int modifyAlpha(int color, int alpha) {
return (color & 0x00ffffff) | (alpha << 24);
}
}
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.os.AsyncTaskCompat;
import android.support.v7.graphics.Palette;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Created by nikola on 1/22/15.
*/
public class LargePalette implements Parcelable {
private List<LargeSwatch> mSwatches = new ArrayList<LargeSwatch>();
private LargeSwatch mVibrantSwatch;
private LargeSwatch mMutedSwatch;
private LargeSwatch mDarkVibrantSwatch;
private LargeSwatch mDarkMutedSwatch;
private LargeSwatch mLightVibrantSwatch;
private LargeSwatch mLightMutedColor;
private static final int DEFAULT_CALCULATE_NUMBER_COLORS = 16;
private LargePalette(List<LargeSwatch> swatches, LargeSwatch vibrantSwatch,
LargeSwatch mutedSwatch, LargeSwatch darkVibrantSwatch,
LargeSwatch darkMutedSwatch, LargeSwatch lightVibrantSwatch,
LargeSwatch lightMutedColor) {
replaceWithPalette(swatches, vibrantSwatch, mutedSwatch, darkVibrantSwatch, darkMutedSwatch, lightVibrantSwatch, lightMutedColor);
}
public static LargePalette generate(Bitmap bitmap, int numColors){
Palette smallerPalette = Palette.generate(bitmap, numColors);
return generateLargerPalette(smallerPalette);
}
public static LargePalette generate(Bitmap bitmap){
return generate(bitmap, DEFAULT_CALCULATE_NUMBER_COLORS);
}
public static AsyncTask<Bitmap, Void, LargePalette> generateAsync(
final Bitmap bitmap, final LargePaletteAsyncListener listener){
return AsyncTaskCompat.executeParallel(new AsyncTask<Bitmap, Void, LargePalette>() {
@Override
protected LargePalette doInBackground(Bitmap... params) {
return generate(params[0]);
}
@Override
protected void onPostExecute(LargePalette largePalette) {
listener.onGenerated(largePalette);
}
}, bitmap);
}
public static AsyncTask<Bitmap, Void, LargePalette> generateAsync(
final Bitmap bitmap, final int numColors, final LargePaletteAsyncListener listener){
return AsyncTaskCompat.executeParallel(new AsyncTask<Bitmap, Void, LargePalette>() {
@Override
protected LargePalette doInBackground(Bitmap... params) {
return generate(params[0], numColors);
}
@Override
protected void onPostExecute(LargePalette largePalette) {
listener.onGenerated(largePalette);
}
}, bitmap);
}
private static LargePalette generateLargerPalette(Palette smallerPalette){
List<LargeSwatch> swatches = generateLargerPaletteSwatches(smallerPalette);
LargeSwatch vibrantSwatch = new LargeSwatch(smallerPalette.getVibrantSwatch());
LargeSwatch mutedSwatch = new LargeSwatch(smallerPalette.getMutedSwatch());
LargeSwatch darkVibrantSwatch = new LargeSwatch(smallerPalette.getDarkVibrantSwatch());
LargeSwatch darkMutedSwatch = new LargeSwatch(smallerPalette.getDarkMutedSwatch());
LargeSwatch lightVibrantSwatch = new LargeSwatch(smallerPalette.getLightVibrantSwatch());
LargeSwatch lightMutedColor = new LargeSwatch(smallerPalette.getLightMutedSwatch());
return new LargePalette(swatches, vibrantSwatch, mutedSwatch,darkVibrantSwatch,darkMutedSwatch,lightVibrantSwatch,lightMutedColor);
}
private void replaceWithPalette(List<LargeSwatch> swatches, LargeSwatch vibrantSwatch,
LargeSwatch mutedSwatch, LargeSwatch darkVibrantSwatch,
LargeSwatch darkMutedSwatch, LargeSwatch lightVibrantSwatch,
LargeSwatch lightMutedColor){
this.mSwatches = swatches;
this.mVibrantSwatch = vibrantSwatch;
this.mMutedSwatch = mutedSwatch;
this.mDarkVibrantSwatch = darkVibrantSwatch;
this.mDarkMutedSwatch = darkMutedSwatch;
this.mLightVibrantSwatch = lightVibrantSwatch;
this.mLightMutedColor = lightMutedColor;
}
private static List<LargeSwatch> generateLargerPaletteSwatches(Palette smallerPalette) {
List<Palette.Swatch> smallerSwatches = smallerPalette.getSwatches();
List<LargeSwatch> largeSwatches = new ArrayList<>(smallerSwatches.size());
for(Palette.Swatch small : smallerSwatches){
largeSwatches.add(new LargeSwatch(small));
}
return Collections.unmodifiableList(largeSwatches);
}
public LargeSwatch getVibrantSwatch() {
return mVibrantSwatch;
}
public void setVibrantSwatch(LargeSwatch vibrantSwatch) {
mVibrantSwatch = vibrantSwatch;
}
public LargeSwatch getMutedSwatch() {
return mMutedSwatch;
}
public void setMutedSwatch(LargeSwatch mutedSwatch) {
mMutedSwatch = mutedSwatch;
}
public LargeSwatch getDarkVibrantSwatch() {
return mDarkVibrantSwatch;
}
public void setDarkVibrantSwatch(LargeSwatch darkVibrantSwatch) {
mDarkVibrantSwatch = darkVibrantSwatch;
}
public LargeSwatch getDarkMutedSwatch() {
return mDarkMutedSwatch;
}
public void setDarkMutedSwatch(LargeSwatch darkMutedSwatch) {
mDarkMutedSwatch = darkMutedSwatch;
}
public LargeSwatch getLightVibrantSwatch() {
return mLightVibrantSwatch;
}
public void setLightVibrantSwatch(LargeSwatch lightVibrantSwatch) {
mLightVibrantSwatch = lightVibrantSwatch;
}
public LargeSwatch getLightMutedColor() {
return mLightMutedColor;
}
public LargeSwatch getLightMutedSwatch() {
return mLightMutedColor;
}
public void setLightMutedColor(LargeSwatch lightMutedColor) {
mLightMutedColor = lightMutedColor;
}
public int getVibrantColor(int defaultColor) {
return mVibrantSwatch != null ? mVibrantSwatch.getRgb() : defaultColor;
}
public int getLightVibrantColor(int defaultColor) {
return mLightVibrantSwatch != null ? mLightVibrantSwatch.getRgb() : defaultColor;
}
public int getDarkVibrantColor(int defaultColor) {
return mDarkVibrantSwatch != null ? mDarkVibrantSwatch.getRgb() : defaultColor;
}
public int getMutedColor(int defaultColor) {
return mMutedSwatch != null ? mMutedSwatch.getRgb() : defaultColor;
}
public int getLightMutedColor(int defaultColor) {
return mLightMutedColor != null ? mLightMutedColor.getRgb() : defaultColor;
}
public int getDarkMutedColor(int defaultColor) {
return mDarkMutedSwatch != null ? mDarkMutedSwatch.getRgb() : defaultColor;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeTypedList(mSwatches);
dest.writeParcelable(this.mVibrantSwatch, 0);
dest.writeParcelable(this.mMutedSwatch, 0);
dest.writeParcelable(this.mDarkVibrantSwatch, 0);
dest.writeParcelable(this.mDarkMutedSwatch, 0);
dest.writeParcelable(this.mLightVibrantSwatch, 0);
dest.writeParcelable(this.mLightMutedColor, 0);
}
private LargePalette(Parcel in) {
in.readTypedList(mSwatches, LargeSwatch.CREATOR);
this.mVibrantSwatch = in.readParcelable(LargeSwatch.class.getClassLoader());
this.mMutedSwatch = in.readParcelable(LargeSwatch.class.getClassLoader());
this.mDarkVibrantSwatch = in.readParcelable(LargeSwatch.class.getClassLoader());
this.mDarkMutedSwatch = in.readParcelable(LargeSwatch.class.getClassLoader());
this.mLightVibrantSwatch = in.readParcelable(LargeSwatch.class.getClassLoader());
this.mLightMutedColor = in.readParcelable(LargeSwatch.class.getClassLoader());
}
public static final Parcelable.Creator<LargePalette> CREATOR = new Parcelable.Creator<LargePalette>() {
public LargePalette createFromParcel(Parcel source) {
return new LargePalette(source);
}
public LargePalette[] newArray(int size) {
return new LargePalette[size];
}
};
public static boolean validatePalette(Palette palette) {
try{
boolean isAnyNull = palette.getVibrantSwatch() != null && palette.getDarkMutedSwatch() != null && palette.getLightMutedSwatch() != null;
if(isAnyNull) {
palette.getVibrantSwatch().getBodyTextColor();
palette.getDarkMutedSwatch().getBodyTextColor();
palette.getLightMutedSwatch().getBodyTextColor();
return true;
}else return isAnyNull;
}catch(IllegalArgumentException e){
e.printStackTrace();
return false;
}catch(AssertionError error){
error.printStackTrace();
return false;
}
}
public static boolean validatePalette(LargePalette palette) {
try{
palette.getVibrantSwatch().getBodyTextColor();
palette.getDarkMutedSwatch().getBodyTextColor();
palette.getLightMutedSwatch().getBodyTextColor();
return true;
}catch(IllegalArgumentException e){
return false;
}
}
public interface LargePaletteAsyncListener{
public void onGenerated(LargePalette largePalette);
}
}
import android.graphics.Color;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v7.graphics.Palette;
import java.util.Arrays;
/**
* Created by nikola on 1/22/15.
*/
public class LargeSwatch implements Parcelable {
private int mRed, mGreen, mBlue;
private int mLargeRgb;
private int mLargePopulation;
private boolean mGeneratedTextColors;
private int mTitleTextColor;
private int mBodyTextColor;
private float[] mHsl;
private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
private static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
/*
Generate LargeSwatch from existing Swatch
*/
public LargeSwatch(Palette.Swatch swatch){
mLargeRgb = swatch.getRgb();
mRed = Color.red(mLargeRgb);
mBlue = Color.blue(mLargeRgb);
mGreen = Color.green(mLargeRgb);
mGeneratedTextColors = true;
mBodyTextColor = swatch.getBodyTextColor();
mTitleTextColor = swatch.getTitleTextColor();
mHsl = swatch.getHsl();
mLargePopulation = swatch.getPopulation();
}
public LargeSwatch(int rgbColor, int population) {
mRed = Color.red(rgbColor);
mGreen = Color.green(rgbColor);
mBlue = Color.blue(rgbColor);
mLargeRgb = rgbColor;
mLargePopulation = population;
}
public LargeSwatch(int red, int green, int blue, int population) {
mRed = red;
mGreen = green;
mBlue = blue;
mLargeRgb = Color.rgb(red, green, blue);
mLargePopulation = population;
}
public int getRgb() {
return mLargeRgb;
}
public float[] getHsl() {
if (mHsl == null) {
mHsl = new float[3];
ColorUtils.RGBtoHSL(mRed, mGreen, mBlue, mHsl);
}
return mHsl;
}
public int getPopulation() {
return mLargePopulation;
}
public int getTitleTextColor() {
ensureTextColorsGenerated();
return mTitleTextColor;
}
public int getBodyTextColor() {
ensureTextColorsGenerated();
return mBodyTextColor;
}
private void ensureTextColorsGenerated() {
if (!mGeneratedTextColors) {
mTitleTextColor = ColorUtils.getTextColorForBackground(mLargeRgb,
MIN_CONTRAST_TITLE_TEXT);
mBodyTextColor = ColorUtils.getTextColorForBackground(mLargeRgb,
MIN_CONTRAST_BODY_TEXT);
mGeneratedTextColors = true;
}
}
@Override
public String toString() {
return new StringBuilder(getClass().getSimpleName())
.append(" [RGB: #").append(Integer.toHexString(getRgb())).append(']')
.append(" [HSL: ").append(Arrays.toString(getHsl())).append(']')
.append(" [Population: ").append(mLargePopulation).append(']')
.append(" [Title Text: #").append(Integer.toHexString(mTitleTextColor)).append(']')
.append(" [Body Text: #").append(Integer.toHexString(mBodyTextColor)).append(']')
.toString();
}
@Override
public int hashCode() {
return 31 * mLargeRgb + mLargePopulation;
}
@Override
public int describeContents() {
return 0;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null) {
return false;
}
if(o instanceof Palette.Swatch) {
Palette.Swatch swatch = (Palette.Swatch) o;
return mLargePopulation == swatch.getPopulation() && mLargeRgb == swatch.getRgb();
}else if(o instanceof LargeSwatch){
LargeSwatch largeSwatch = (LargeSwatch) o;
return mLargePopulation == largeSwatch.getPopulation() && mLargeRgb == largeSwatch.getRgb();
}
return false;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(this.mRed);
dest.writeInt(this.mGreen);
dest.writeInt(this.mBlue);
dest.writeInt(this.mLargeRgb);
dest.writeInt(this.mLargePopulation);
dest.writeByte(mGeneratedTextColors ? (byte) 1 : (byte) 0);
dest.writeInt(this.getTitleTextColor());
dest.writeInt(this.getBodyTextColor());
dest.writeFloatArray(this.getHsl());
}
private LargeSwatch(Parcel in) {
this.mRed = in.readInt();
this.mGreen = in.readInt();
this.mBlue = in.readInt();
this.mLargeRgb = in.readInt();
this.mLargePopulation = in.readInt();
this.mGeneratedTextColors = in.readByte() != 0;
this.mTitleTextColor = in.readInt();
this.mBodyTextColor = in.readInt();
this.mHsl = in.createFloatArray();
}
public static final Parcelable.Creator<LargeSwatch> CREATOR = new Parcelable.Creator<LargeSwatch>() {
public LargeSwatch createFromParcel(Parcel source) {
return new LargeSwatch(source);
}
public LargeSwatch[] newArray(int size) {
return new LargeSwatch[size];
}
};
}
@LouisCAD
Copy link

Why don't you contribute directly to Android source for Support Library?

@NikolaDespotoski
Copy link
Author

I wish. That's not easy. :D

@h0tfix
Copy link

h0tfix commented Aug 16, 2016

Thanks dude, very helpful classes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment