/**
* Copyright (c) 2014-2017 by the respective copyright holders.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*/
package org.eclipse.smarthome.core.library.types;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.SortedMap;
import java.util.TreeMap;
import org.eclipse.smarthome.core.library.internal.StateConverterUtil;
import org.eclipse.smarthome.core.types.Command;
import org.eclipse.smarthome.core.types.ComplexType;
import org.eclipse.smarthome.core.types.Convertible;
import org.eclipse.smarthome.core.types.PrimitiveType;
import org.eclipse.smarthome.core.types.State;
/**
* The HSBType is a complex type with constituents for hue, saturation and
* brightness and can be used for color items.
*
* @author Kai Kreuzer - Initial contribution and API
* @author Chris Jackson - Added fromRGB
*
*/
public class HSBType extends PercentType implements ComplexType, State, Command, Convertible {
private static final long serialVersionUID = 322902950356613226L;
// constants for the constituents
static final public String KEY_HUE = "h";
static final public String KEY_SATURATION = "s";
static final public String KEY_BRIGHTNESS = "b";
// constants for colors
static final public HSBType BLACK = new HSBType("0,0,0");
static final public HSBType WHITE = new HSBType("0,0,100");
static final public HSBType RED = new HSBType("0,100,100");
static final public HSBType GREEN = new HSBType("120,100,100");
static final public HSBType BLUE = new HSBType("240,100,100");
protected BigDecimal hue;
protected BigDecimal saturation;
public HSBType() {
this("0,0,0");
}
public HSBType(DecimalType h, PercentType s, PercentType b) {
this.hue = h.toBigDecimal();
this.saturation = s.toBigDecimal();
this.value = b.toBigDecimal();
}
public HSBType(String value) {
if (value != null) {
String[] constituents = value.split(",");
if (constituents.length == 3) {
this.hue = new BigDecimal(constituents[0]);
this.saturation = new BigDecimal(constituents[1]);
this.value = new BigDecimal(constituents[2]);
} else {
throw new IllegalArgumentException(value + " is not a valid HSBType syntax");
}
} else {
throw new IllegalArgumentException("Constructor argument must not be null");
}
}
public static HSBType valueOf(String value) {
return new HSBType(value);
}
/**
* Create HSB from RGB
*
* @param r red 0-255
* @param g green 0-255
* @param b blue 0-255
*/
public static HSBType fromRGB(int r, int g, int b) {
float tmpHue, tmpSaturation, tmpBrightness;
int max = (r > g) ? r : g;
if (b > max) {
max = b;
}
int min = (r < g) ? r : g;
if (b < min) {
min = b;
}
tmpBrightness = max / 2.55f;
tmpSaturation = (max != 0 ? ((float) (max - min)) / ((float) max) : 0) * 100;
if (tmpSaturation == 0) {
tmpHue = 0;
} else {
float red = ((float) (max - r)) / ((float) (max - min));
float green = ((float) (max - g)) / ((float) (max - min));
float blue = ((float) (max - b)) / ((float) (max - min));
if (r == max) {
tmpHue = blue - green;
} else if (g == max) {
tmpHue = 2.0f + red - blue;
} else {
tmpHue = 4.0f + green - red;
}
tmpHue = tmpHue / 6.0f * 360;
if (tmpHue < 0) {
tmpHue = tmpHue + 360.0f;
}
}
return new HSBType(new DecimalType((int) tmpHue), new PercentType((int) tmpSaturation),
new PercentType((int) tmpBrightness));
}
@Override
public SortedMap<String, PrimitiveType> getConstituents() {
TreeMap<String, PrimitiveType> map = new TreeMap<String, PrimitiveType>();
map.put(KEY_HUE, getHue());
map.put(KEY_SATURATION, getSaturation());
map.put(KEY_BRIGHTNESS, getBrightness());
return map;
}
public DecimalType getHue() {
return new DecimalType(hue);
}
public PercentType getSaturation() {
return new PercentType(saturation);
}
public PercentType getBrightness() {
return new PercentType(value);
}
public PercentType getRed() {
return toRGB()[0];
}
public PercentType getGreen() {
return toRGB()[1];
}
public PercentType getBlue() {
return toRGB()[2];
}
/**
* Returns the RGB value representing the color in the default sRGB
* color model.
* (Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue).
*
* @return the RGB value of the color in the default sRGB color model
*/
public int getRGB() {
PercentType[] rgb = toRGB();
return ((0xFF) << 24) | ((convertPercentToByte(rgb[0]) & 0xFF) << 16)
| ((convertPercentToByte(rgb[1]) & 0xFF) << 8) | ((convertPercentToByte(rgb[2]) & 0xFF) << 0);
}
@Override
public String toString() {
return toFullString();
}
@Override
public String toFullString() {
return getHue() + "," + getSaturation() + "," + getBrightness();
}
@Override
public int hashCode() {
int tmp = 10000 * (getHue() == null ? 0 : getHue().hashCode());
tmp += 100 * (getSaturation() == null ? 0 : getSaturation().hashCode());
tmp += (getBrightness() == null ? 0 : getBrightness().hashCode());
return tmp;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof HSBType)) {
return false;
}
HSBType other = (HSBType) obj;
if ((getHue() != null && other.getHue() == null) || (getHue() == null && other.getHue() != null)
|| (getSaturation() != null && other.getSaturation() == null)
|| (getSaturation() == null && other.getSaturation() != null)
|| (getBrightness() != null && other.getBrightness() == null)
|| (getBrightness() == null && other.getBrightness() != null)) {
return false;
}
if (!getHue().equals(other.getHue()) || !getSaturation().equals(other.getSaturation())
|| !getBrightness().equals(other.getBrightness())) {
return false;
}
return true;
}
public PercentType[] toRGB() {
PercentType red = null;
PercentType green = null;
PercentType blue = null;
BigDecimal h = hue.divide(BigDecimal.valueOf(100), 10, BigDecimal.ROUND_HALF_UP);
BigDecimal s = saturation.divide(BigDecimal.valueOf(100));
int h_int = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, BigDecimal.ROUND_HALF_UP)
.intValue();
BigDecimal f = h.multiply(BigDecimal.valueOf(5)).divide(BigDecimal.valueOf(3), 10, BigDecimal.ROUND_HALF_UP)
.remainder(BigDecimal.ONE);
PercentType a = new PercentType(value.multiply(BigDecimal.ONE.subtract(s)));
PercentType b = new PercentType(value.multiply(BigDecimal.ONE.subtract(s.multiply(f))));
PercentType c = new PercentType(
value.multiply(BigDecimal.ONE.subtract((BigDecimal.ONE.subtract(f)).multiply(s))));
if (h_int == 0 || h_int == 6) {
red = getBrightness();
green = c;
blue = a;
} else if (h_int == 1) {
red = b;
green = getBrightness();
blue = a;
} else if (h_int == 2) {
red = a;
green = getBrightness();
blue = c;
} else if (h_int == 3) {
red = a;
green = b;
blue = getBrightness();
} else if (h_int == 4) {
red = c;
green = a;
blue = getBrightness();
} else if (h_int == 5) {
red = getBrightness();
green = a;
blue = b;
} else {
throw new RuntimeException();
}
return new PercentType[] { red, green, blue };
}
private int convertPercentToByte(PercentType percent) {
return percent.value.multiply(BigDecimal.valueOf(255))
.divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP).intValue();
}
@Override
public State as(Class<? extends State> target) {
if (target == OnOffType.class) {
// if brightness is not completely off, we consider the state to be on
return getBrightness().equals(PercentType.ZERO) ? OnOffType.OFF : OnOffType.ON;
} else if (target == DecimalType.class) {
return new DecimalType(getBrightness().toBigDecimal().divide(BigDecimal.valueOf(100), 8, RoundingMode.UP));
} else if (target == PercentType.class) {
return new PercentType(getBrightness().toBigDecimal());
} else {
return StateConverterUtil.defaultConversion(this, target);
}
}
}