/* package whatever; // don't place package name! */
import android.graphics.Color;
import android.graphics.PointF;
import android.support.annotation.IntRange;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import timber.log.Timber;
public class ColorHelper {
private static final int cptRED = 0;
private static final int cptGREEN = 1;
private static final int cptBLUE = 2;
return hsvToRgb(hue, sat, 1.0d);
}
Double f
= (hue
* 6.0d
) - ((double) i
); Double p
= val
* (1.0d
- sat
); Double q
= val
* (1.0d
- (f
* sat
)); Double t
= val
* (1.0d
- (1.0d
- f
) * sat
); switch (i % 6) {
case 0:
r = val;
g = t;
b = p;
break;
case 1:
r = q;
g = val;
b = p;
break;
case 2:
r = p;
g = val;
b = t;
break;
case 3:
r = p;
g = q;
b = val;
break;
case 4:
r = t;
g = p;
b = val;
break;
case 5:
r = val;
g = p;
b = q;
break;
}
if (StringR.length() == 1) {
StringR = "0" + StringR;
}
if (StringG.length() == 1) {
StringG = "0" + StringG;
}
if (StringB.length() == 1) {
StringB = "0" + StringB;
}
return StringR + StringG + StringB;
}
public static String ctToRgb
(int ct
) { double red;
double green;
double blue;
double temp = (ctToKelvin(ct) + 1000.0d) / 100.0d;
if (temp <= 66.0d) {
red = 255.0d;
} else {
red
= 329.698727446d
* Math.
pow(temp
- 60.0d,
-0.1332047592d
); if (red < 0.0d) {
red = 0.0d;
}
if (red > 255.0d) {
red = 255.0d;
}
}
if (temp <= 66.0d) {
green
= (99.4708025861d
* Math.
log1p(temp
)) - 161.1195681661d
; if (green < 0.0d) {
green = 0.0d;
}
if (green > 255.0d) {
green = 255.0d;
}
} else {
green
= 288.1221695283d
* Math.
pow(temp
- 60.0d,
-0.0755148492d
); if (green < 0.0d) {
green = 0.0d;
}
if (green > 255.0d) {
green = 255.0d;
}
}
if (temp >= 66.0d) {
blue = 255.0d;
} else if (temp <= 19.0d) {
blue = 0.0d;
} else {
blue
= (130.5177312231d
* Math.
log1p(temp
- 10.0d
)) - 305.0447927307d
; if (blue < 0.0d) {
blue = 0.0d;
}
if (blue > 255.0d) {
blue = 255.0d;
}
}
if (r.length() == 1) {
r = "0" + r;
}
if (g.length() == 1) {
g = "0" + g;
}
if (b.length() == 1) {
b = "0" + b;
}
return r + g + b;
}
public static int ctToKelvin(int ct) {
return clamp(1000000 / ct, 2000, 6500);
}
public static int kelvinToCt(int kelvin) {
return clamp(1000000 / kelvin, 153, 500);
}
public static int ctToColor(int ct) {
String hex
= "#" + ctToRgb
(ct
); return Color.
parseColor(hex
); }
/**
* Checks whether the specified value is between (including bounds) 0 and 255
*
* @param colorValue Color value
* @return Specified input value if between 0 and 255, otherwise 0
*/
public static int assertColorValueInRange(@IntRange(from = 0, to = 255) int colorValue) {
return ((0 <= colorValue) && (colorValue <= 255)) ? colorValue : 0;
}
/**
* Formats individual RGB values to be output as a HEX string.
* <p>
* Beware: If color value is lower than 0 or higher than 255, it's reset to 0.
*
* @param red Red color value
* @param green Green color value
* @param blue Blue color value
* @return HEX String containing the three values
*/
public static String formatColorValues
( @IntRange(from = 0, to = 255) int red,
@IntRange(from = 0, to = 255) int green,
@IntRange(from = 0, to = 255) int blue) {
return String.
format("%02X%02X%02X",
assertColorValueInRange(red),
assertColorValueInRange(green),
assertColorValueInRange(blue)
);
}
public static String formatColorValues
(int color,
boolean alpha
) { return String.
format("%06X",
(0xFFFFFF
& color
)); }
/**
* Formats individual ARGB values to be output as an 8 character HEX string.
* <p>
* Beware: If any value is lower than 0 or higher than 255, it's reset to 0.
*
* @param alpha Alpha value
* @param red Red color value
* @param green Green color value
* @param blue Blue color value
* @return HEX String containing the three values
* @since v1.1.0
*/
public static String formatColorValues
( @IntRange(from = 0, to = 255) int alpha,
@IntRange(from = 0, to = 255) int red,
@IntRange(from = 0, to = 255) int green,
@IntRange(from = 0, to = 255) int blue) {
return String.
format("%02X%02X%02X%02X",
assertColorValueInRange(alpha),
assertColorValueInRange(red),
assertColorValueInRange(green),
assertColorValueInRange(blue)
);
}
public static double[] calculateFFT(byte[] signal) {
final int mNumberOfFFTPoints = 1024;
double mMaxFFTSample;
double temp;
Complex[] y;
Complex[] complexSignal = new Complex[mNumberOfFFTPoints];
double[] absSignal = new double[mNumberOfFFTPoints / 2];
for (int i = 0; i < mNumberOfFFTPoints; i++) {
temp = (double) ((signal[2 * i] & 0xFF) | (signal[2 * i + 1] << 8)) / 32768.0F;
complexSignal[i] = new Complex(temp, 0.0);
}
y = FFT.INSTANCE.fft(complexSignal);
mMaxFFTSample = 0.0;
for (int i = 0; i < (mNumberOfFFTPoints / 2); i++) {
absSignal
[i
] = Math.
sqrt(Math.
pow(y
[i
].
re(),
2) + Math.
pow(y
[i
].
im(),
2)); if (absSignal[i] > mMaxFFTSample) {
mMaxFFTSample = absSignal[i];
}
}
return absSignal;
}
public static List<Integer> calculateColorsFromFFT(double[] data, int RECORDER_SAMPLERATE) {
List<Integer> numbers = new ArrayList<>();
double maxDouble = 0.0;
if (d * RECORDER_SAMPLERATE > maxDouble) {
maxDouble = d;
}
Double t
= d
* RECORDER_SAMPLERATE
; if (t.intValue() < 65535)
numbers.add(t.intValue());
}
return numbers;
}
public static int xyToTemperature(PointF xy) {
float x = xy.x;
float y = xy.y;
double temp2
= (437 * Math.
pow((x
- 0.332) / (0.1858 - y
),
3) + 3601 * Math.
pow((x
- 0.332) / (0.1858 - y
),
2) + 6831 * ((x - 0.332) / (0.1858 - y))) +
5517;
float micro2 = (float) (1 / temp2 * 1000000);
return clamp((int) micro2, 153, 500);
}
static public int colorFromXY
(PointF xy,
String model
) { List<PointF> colorPoints = colorPointsForModel(model);
boolean inReachOfLamps = checkPointInLampsReach(xy, colorPoints);
Timber.e("Color was not in reach of lamps");
if (!inReachOfLamps) {
// It seems the colour is out of reach; let's find the closest colour we can produce with our lamp and
// send this XY value out.
// Find the closest point on each line in the triangle
PointF pAB = getClosestPointToPoints(colorPoints.get(cptRED), colorPoints.get(cptGREEN), xy);
PointF pAC = getClosestPointToPoints(colorPoints.get(cptBLUE), colorPoints.get(cptRED), xy);
PointF pBC = getClosestPointToPoints(colorPoints.get(cptGREEN), colorPoints.get(cptBLUE), xy);
// Get the distances per point and see which point is closer to our Point
float dAB = getDistanceBetweenTwoPoints(xy, pAB);
float dAC = getDistanceBetweenTwoPoints(xy, pAC);
float dBC = getDistanceBetweenTwoPoints(xy, pBC);
float lowest = dAB;
PointF closestPoint = pAB;
if (dAC < lowest) {
lowest = dAC;
closestPoint = pAC;
}
if (dBC < lowest) {
closestPoint = pBC;
}
// Change the xy value to a value which is within the reach of the lamp
xy.x = closestPoint.x;
xy.y = closestPoint.y;
}
float x = xy.x;
float y = xy.y;
float z = 1.0f - x - y;
float Y = 1.0f;
float X = (Y / y) * x;
float Z = (Y / y) * z;
// sRGB D65 conversion
float r = X * 3.2406f - Y * 1.5372f - Z * 0.4986f;
float g = -X * 0.9689f + Y * 1.8758f + Z * 0.0415f;
float b = X * 0.0557f - Y * 0.2040f + Z * 1.0570f;
if (r > b && r > g && r > 1.0f) {
// red is too big
g = g / r;
b = b / r;
r = 1.0f;
} else if (g > b && g > r && g > 1.0f) {
// green is too big
r = r / g;
b = b / g;
g = 1.0f;
} else if (b > r && b > g && b > 1.0f) {
// blue is too big
r = r / b;
g = g / b;
b = 1.0f;
}
// Apply gamma correction
r
= r
<= 0.0031308f
? 12.92f
* r
: (1.0f
+ 0.055f
) * (float) Math.
pow((double) r,
(double) ((1.0f
/ 2.4f
))) - 0.055f
; g
= g
<= 0.0031308f
? 12.92f
* g
: (1.0f
+ 0.055f
) * (float) Math.
pow((double) g,
(double) ((1.0f
/ 2.4f
))) - 0.055f
; b
= b
<= 0.0031308f
? 12.92f
* b
: (1.0f
+ 0.055f
) * (float) Math.
pow((double) b,
(double) ((1.0f
/ 2.4f
))) - 0.055f
;
if (r > b && r > g) {
// red is biggest
if (r > 1.0f) {
g = g / r;
b = b / r;
r = 1.0f;
}
} else if (g > b && g > r) {
// green is biggest
if (g > 1.0f) {
r = r / g;
b = b / g;
g = 1.0f;
}
} else if (b > r && b > g) {
// blue is biggest
if (b > 1.0f) {
r = r / b;
g = g / b;
b = 1.0f;
}
}
return fromFloatRGB(r, g, b);
}
static public PointF calculateXY
(int color,
String model
) { return calculateXY
(Color.
red(color
),
Color.
green(color
),
Color.
blue(color
), model
); }
static public PointF calculateXY
(int aRed,
int aGreen,
int aBlue,
String model
) { double normalizedRed = (double) aRed / 255.0;
double normalizedGreen = (double) aGreen / 255.0;
double normalizedBlue = (double) aBlue / 255.0;
// apply gamma correction
float red
= (normalizedRed
> 0.04045f
) ? (float) Math.
pow((normalizedRed
+ 0.055) / (1.0 + 0.055),
2.4) : (float) (normalizedRed
/ 12.92); float green
= (normalizedGreen
> 0.04045) ? (float) Math.
pow((normalizedGreen
+ 0.055) / (1.0 + 0.055),
2.4) : (float) (normalizedGreen
/ 12.92); float blue
= (normalizedBlue
> 0.04045) ? (float) Math.
pow((normalizedBlue
+ 0.055) / (1.0 + 0.055),
2.4) : (float) (normalizedBlue
/ 12.92);
// wide gamut conversion D65
float X = (red * 0.649926f + green * 0.103455f + blue * 0.197109f);
float Y = (red * 0.234327f + green * 0.743075f + blue * 0.022598f);
float Z = (red * 0.0000000f + green * 0.053077f + blue * 1.035763f);
float cx = X / (X + Y + Z);
float cy = Y / (X + Y + Z);
PointF xyPoint = new PointF(cx, cy);
List<PointF> colorPoints = colorPointsForModel(model);
boolean inReachOfLamps = checkPointInLampsReach(xyPoint, colorPointsForModel(model));
if (!inReachOfLamps) {
// It seems the colour is out of reach let's find the closest colour we can produce with our lamp and
// send this XY value out
// Find the closest point on each line in the triangle
PointF pAB = getClosestPointToPoints(colorPoints.get(cptRED), colorPoints.get(cptGREEN), xyPoint);
PointF pAC = getClosestPointToPoints(colorPoints.get(cptBLUE), colorPoints.get(cptRED), xyPoint);
PointF pBC = getClosestPointToPoints(colorPoints.get(cptGREEN), colorPoints.get(cptBLUE), xyPoint);
// Get the distances per point and see which point is closer to our Point
float dAB = getDistanceBetweenTwoPoints(xyPoint, pAB);
float dAC = getDistanceBetweenTwoPoints(xyPoint, pAC);
float dBC = getDistanceBetweenTwoPoints(xyPoint, pBC);
float lowest = dAB;
PointF closestPoint = pAB;
if (dAC < lowest) {
lowest = dAC;
closestPoint = pAC;
}
if (dBC < lowest) {
closestPoint = pBC;
}
// Change the xy value to a value which is within reach of the lamp
cx = closestPoint.x;
cy = closestPoint.y;
}
return new PointF(cx, cy);
}
static public List
<PointF
> colorPointsForModel
(String model
) { List<PointF> colorPoints = new ArrayList<PointF>();
List
<String
> hueBulbs
= Arrays.
asList( "LCT001", // Hue A19
"LCT002", // Hue BR30
"LCT003" // Hue GU10
);
List
<String
> livingColors
= Arrays.
asList( "LLC001", // Monet, Renoir, Mondriaan (gen II)
"LLC005", // Bloom (gen II)
"LLC006", // Iris (gen III)
"LLC007", // Bloom, Aura (gen III)
"LLC011", // Hue Bloom
"LLC012", // Hue Bloom
"LLC013", // Storylight
"LST001" // Light Strips
);
if (hueBulbs.contains(model)) {
// Hue bulbs color gamut triangle
colorPoints.add(new PointF(0.674f, 0.322f)); // Red
colorPoints.add(new PointF(0.408f, 0.517f)); // Green
colorPoints.add(new PointF(0.168f, 0.041f)); // Blue
} else if (livingColors.contains(model)) {
// LivingColors color gamut triangle
colorPoints.add(new PointF(0.703f, 0.296f)); // Red
colorPoints.add(new PointF(0.214f, 0.709f)); // Green
colorPoints.add(new PointF(0.139f, 0.081f)); // Blue
} else {
// Default construct triangle which contains all values
colorPoints.add(new PointF(1.0f, 0.0f)); // Red
colorPoints.add(new PointF(0.0f, 1.0f)); // Green
colorPoints.add(new PointF(0.0f, 0.0f)); // Blue
}
return colorPoints;
}
static public PointF getClosestPointToPoints(PointF A, PointF B, PointF P) {
PointF AP = new PointF(P.x - A.x, P.y - A.y);
PointF AB = new PointF(B.x - A.x, B.y - A.y);
float ab2 = AB.x * AB.x + AB.y * AB.y;
float ap_ab = AP.x * AB.x + AP.y * AB.y;
float t = ap_ab / ab2;
if (t < 0.0f) {
t = 0.0f;
} else if (t > 1.0f) {
t = 1.0f;
}
return new PointF(A.x + AB.x * t, A.y + AB.y * t);
}
private static float getDistanceBetweenTwoPoints(PointF one, PointF two) {
float dx = one.x - two.x; // horizontal difference
float dy = one.y - two.y; // vertical difference
return (float) Math.
sqrt(dx
* dx
+ dy
* dy
); }
private static boolean checkPointInLampsReach(PointF p, List<PointF> colorPoints) {
PointF red = colorPoints.get(cptRED);
PointF green = colorPoints.get(cptGREEN);
PointF blue = colorPoints.get(cptBLUE);
PointF v1 = new PointF(green.x - red.x, green.y - red.y);
PointF v2 = new PointF(blue.x - red.x, blue.y - red.y);
PointF q = new PointF(p.x - red.x, p.y - red.y);
float s = crossProduct(q, v2) / crossProduct(v1, v2);
float t = crossProduct(v1, q) / crossProduct(v1, v2);
return (s >= 0.0f) && (t >= 0.0f) && (s + t <= 1.0f);
}
private static float crossProduct(PointF p1, PointF p2) {
return (p1.x * p2.y - p1.y * p2.x);
}
private static int fromFloatRGB(float red, float green, float blue) {
int r
= Math.
max((int) Math.
ceil(red
* 255),
0); int g
= Math.
max((int) Math.
ceil(green
* 255),
0); int b
= Math.
max((int) Math.
ceil(blue
* 255),
0); return Color.
rgb(r, g, b
); }
public static int briToAlpha(int bri) {
return (int) (bri / 2.54);
}
public static int alphaToBri(int bri) {
return (int) (bri * 2.54);
}
public static int clamp(int value, int min, int max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
public static float clamp(float value, float min, float max) {
if (value < min) return min;
if (value > max) return max;
return value;
}
public static int getComplementaryColor(int colorToInvert) {
float[] hsv = new float[3];
Color.
blue(colorToInvert
), hsv
); hsv[0] = (hsv[0] + 180) % 360;
return Color.
HSVToColor(hsv
); }
}