/*
* Copyright (C) 2011 The original author or authors.
*
* 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.zapta.apps.maniana.util;
import static com.zapta.apps.maniana.util.Assertions.check;
import com.zapta.apps.maniana.annotations.ApplicationScope;
import com.zapta.apps.maniana.annotations.VisibleForTesting;
import android.graphics.Color;
/**
* @author Tal Dayan
*/
@ApplicationScope
public final class ColorUtil {
/** Do not instantiate */
private ColorUtil() {
}
/**
* Returns candidate color furtherest from given reference color. In case of a tie, prefer
* candidates with lower indices. Candidates array must contain at least one member. Alpha
* channel is ignored.
*/
public static final int selectFurthestColor(int referenceColor, int candidates[],
float defaultPreference) {
final int index = selectFurthestColorIndex(referenceColor, candidates, defaultPreference);
return candidates[index];
}
/**
* Returns index of candidate color furtherest from given reference color. In case of a tie,
* prefer candidates with lower indices. Candidates array must contain at least one member.
* Alpha channel is ignored.
*
* @param defaultPreference a float in the range [0,1] indicating the left of preference to give
* to first candidate.
*/
public static final int selectFurthestColorIndex(int referenceColor, int candidates[],
float defaultPreference) {
check(candidates.length > 0);
final int r = Color.red(referenceColor);
final int g = Color.green(referenceColor);
final int b = Color.blue(referenceColor);
int bestCandidateIndex = -1;
int bestDistance = -1;
for (int i = 0; i < candidates.length; i++) {
final int nextColor = candidates[i];
int nextDistance = distance(r, g, b, nextColor);
check(nextDistance >= 0);
// If default candidate, skew the distance by preference.:w
if (i == 0) {
nextDistance += (int) (255 * 3 * defaultPreference);
}
if (nextDistance > bestDistance) {
bestCandidateIndex = i;
bestDistance = nextDistance;
}
}
check(bestCandidateIndex >= 0);
return bestCandidateIndex;
}
/**
* Compute the distance between two colors. The alpha channel is ignored. Returned distance is
* always >= 0.
*/
public static final int distance(int r, int g, int b, int color) {
return Math.abs(r - Color.red(color)) + Math.abs(g - Color.green(color))
+ Math.abs(b - Color.blue(color));
}
/**
* Compute the combined alpha of a laver with alpha a2 over a layer with alpha a1. a1, a2 are in
* the range [0..255]. Where 0 indicates transparent and 255 indicates opaque color. The method
* does not perform range checking. Providing out of range value parameters in undefined result.
*
* Formula is based on http://tinyurl.com/79dmuay.
*/
@VisibleForTesting
static int compositeAlpha(int a1, int a2) {
return 255 - ((255 - a2) * (255 - a1)) / 255;
}
/**
* Compute the combined color component (R, G or B) of layer 2 over layer 1.
*
* @param c1 color of layer 1 [0 .. 255]
* @param a1 alpha of layer 1 [0 .. 255]
* @param c2 color of layer 2 [0 .. 255]
* @param a2 alpha of layer 1 [0 .. 255]
* @param a compositeAlpha(a1, a2)
* @return the composite color component [0..255]
*/
@VisibleForTesting
static int compositeColorComponent(int c1, int a1, int c2, int a2, int a) {
// Handle the singular case of both layers fully transparent.
if (a == 0) {
return 0x00;
}
return (((255 * c2 * a2) + (c1 * a1 * (255 - a2))) / a) / 255;
}
/**
* Compute the combined color of a layer with color argb2 over layer with color argb1.
*/
public static int compositeColor(int argb1, int argb2) {
final int a1 = Color.alpha(argb1);
final int a2 = Color.alpha(argb2);
final int a = compositeAlpha(a1, a2);
final int r = compositeColorComponent(Color.red(argb1), a1, Color.red(argb2), a2, a);
final int g = compositeColorComponent(Color.green(argb1), a1, Color.green(argb2), a2, a);
final int b = compositeColorComponent(Color.blue(argb1), a1, Color.blue(argb2), a2, a);
return Color.argb(a, r, g, b);
}
/** Map background color preference to paper overlay color. */
public static int mapPaperColorPrefernce(int paperColorPreference) {
final float hsv[] = new float[3];
Color.colorToHSV(paperColorPreference, hsv);
// Map saturation to alpha. The paper bitmap below the template will provide
// the white background.
final int alpha = (int) (hsv[1] * 255);
// Saturation and value are set to max.
hsv[1] = 1.f;
hsv[2] = 1.f;
return Color.HSVToColor(alpha, hsv);
}
}