Skip to content

Instantly share code, notes, and snippets.

@awwsmm
Last active October 29, 2017 21:11
Show Gist options
  • Save awwsmm/1bf4557e6205edb37da5d7f701e14aca to your computer and use it in GitHub Desktop.
Save awwsmm/1bf4557e6205edb37da5d7f701e14aca to your computer and use it in GitHub Desktop.
Scala extension to java.awt.Color with Hex, HSV, RGB, and decimal methods, color conversions, and gradient methods
import java.awt.Color
import scala.math._
////////////////////////////////////////////////////////////////////////////////
///
/// SColor extends java.awt.Color and provides the additional methods:
///
/// asRGB(): List[Int]
/// provides a (red: Int, green: Int, blue: Int) representation of the
/// color, where each component has a range [0,255]
///
/// asHEX(): String
/// provides a hexadecimal string representation of the color, ie. "F9E013"
///
/// asHSV(): List[Double]
/// provides a (hue: Double, saturation: Double, value: Double) represent-
/// ation of the color, where each component has a range [0.0,1.0]
///
/// asDEC(): Int
/// provides an integer representation of the color, where "the red
/// component in bits 16-23, the green component in bits 8-15, and the blue
/// component in bits 0-7", per java.awt.Color standards
///
/// color1[SColor].gradientRGB(color2: SColor, nSteps: Int): List[String]
/// given an initial SColor color1 and a final SColor color2, this method
/// returns a list (length: nSteps >= 1) of hexadecimal strings representing
/// the evenly-spaced intermediate colors in RGB space
///
/// color1[SColor].gradientHSV(color2: SColor, nSteps: Int, inverse: Boolean): List[String]
/// same as above, but in HSV space. The option "inverse" allows the HSV
/// space to be travelled anticlockwise in hue, rather than clockwise.
///
/// This class uses Double values instead of Java floats, for easier use in
/// Scala.
///
////////////////////////////////////////////////////////////////////////////////
class SColor private ( r: Float, g: Float, b: Float, a: Float ) extends java.awt.Color (r, g, b, a) {
///---------------------------------------------------------------------------
/// constructors
///---------------------------------------------------------------------------
// java.awt.Color
def this(color: Color) = { this(
(color.getRed /255.0).toFloat, // r
(color.getGreen/255.0).toFloat, // g
(color.getBlue /255.0).toFloat, // b
(color.getAlpha/255.0).toFloat // a
)}
/// RGB constructors
// Double: r, g, b
def this(r: Double, g: Double, b: Double) = { this(
r.toFloat, // r
g.toFloat, // g
b.toFloat, // b
1.0f // a
)}
// Double: r, g, b, a
def this(r: Double, g: Double, b: Double, a: Double) = { this(
r.toFloat, // r
g.toFloat, // g
b.toFloat, // b
a.toFloat // a
)}
// Int: r, g, b
def this(r: Int, g: Int, b: Int) = { this(
(r/255.0).toFloat, // r
(g/255.0).toFloat, // g
(b/255.0).toFloat, // b
1.0f // a
)}
// Int: r, g, b, a
def this(r: Int, g: Int, b: Int, a: Int) = { this(
(r/255.0).toFloat, // r
(g/255.0).toFloat, // g
(b/255.0).toFloat, // b
(a/255.0).toFloat // a
)}
/// HSV constructors
// Double: h, s, v
def this(h: Double, s: Double, v: Double, isHSV: Boolean) = { this(
// r
if (isHSV) {
val rgb = Color.HSBtoRGB(h.toFloat, s.toFloat, v.toFloat);
(((rgb & 0x00ff0000) >> 16)/255.0).toFloat
} else {
h.toFloat
},
// g
if (isHSV) {
val rgb = Color.HSBtoRGB(h.toFloat, s.toFloat, v.toFloat);
(((rgb & 0x0000ff00) >> 8)/255.0).toFloat
} else {
s.toFloat
},
// b
if (isHSV) {
val rgb = Color.HSBtoRGB(h.toFloat, s.toFloat, v.toFloat);
(((rgb & 0x000000ff) )/255.0).toFloat
} else {
v.toFloat
},
// a
1.0f
)}
// Int: h, s, v
def this(h: Int, s: Int, v: Int, isHSV: Boolean) = { this(
// r
if (isHSV) {
val hsv = Color.HSBtoRGB((h/255.0).toFloat, (s/255.0).toFloat, (v/255.0).toFloat);
(((hsv & 0x00ff0000) >> 16)/255.0).toFloat
} else {
(h/255.0).toFloat
},
// g
if (isHSV) {
val hsv = Color.HSBtoRGB((h/255.0).toFloat, (s/255.0).toFloat, (v/255.0).toFloat);
(((hsv & 0x0000ff00) >> 8)/255.0).toFloat
} else {
(s/255.0).toFloat
},
// b
if (isHSV) {
val hsv = Color.HSBtoRGB((h/255.0).toFloat, (s/255.0).toFloat, (v/255.0).toFloat);
(((hsv & 0x000000ff) )/255.0).toFloat
} else {
(v/255.0).toFloat
},
// a
1.0f
)}
/// HEX constructors
// String: hex
def this(hex: String) = { this(
(Integer.parseInt(hex.substring(0,2), 16)/255.0).toFloat, // r
(Integer.parseInt(hex.substring(2,4), 16)/255.0).toFloat, // g
(Integer.parseInt(hex.substring(4,6), 16)/255.0).toFloat, // b
1.0f // a
)}
/// DEC constructors
// Int: rgba
def this(rgba: Int, hasalpha: Boolean) = { this(
(((rgba & 0x00ff0000) >> 16)/255.0).toFloat, // r
(((rgba & 0x0000ff00) >> 8)/255.0).toFloat, // g
(((rgba & 0x000000ff) )/255.0).toFloat, // b
if (hasalpha)
(((rgba & 0xff000000) >> 24) & 0x000000ff).toFloat // a
else
1.0f
)}
// Int: rgb
def this(rgb: Int) = { this(
(((rgb & 0x00ff0000) >> 16)/255.0).toFloat, // r
(((rgb & 0x0000ff00) >> 8)/255.0).toFloat, // g
(((rgb & 0x000000ff) )/255.0).toFloat, // b
1.0f // a
)}
///---------------------------------------------------------------------------
/// methods: different representations of this color
///---------------------------------------------------------------------------
def asRGB: List[Int] = {
List((r*255).toInt, (g*255).toInt, (b*255).toInt)
}
def asHEX: String = {
val rgb = this.asRGB
String.format("%2s%2s%2s",
rgb(0).toHexString.toUpperCase,
rgb(1).toHexString.toUpperCase,
rgb(2).toHexString.toUpperCase).replace(' ','0')
}
def asHSV: List[Double] = {
val rgb = this.asRGB
Color.RGBtoHSB(rgb(0), rgb(1), rgb(2), null).to[List].map(_.toDouble)
}
def asDEC: Int = {
val hex = this.asHEX
Integer.parseInt(hex, 16)
}
///---------------------------------------------------------------------------
/// methods: gradients
///---------------------------------------------------------------------------
// forward and backward gradients through HSV space
def gradientHSV(that: SColor, nSteps: Int = 10, inverse: Boolean = false): List[String] = {
val (color1, color2) = (this.asHSV, that.asHSV)
var (hStart, hEnd, hRange) = (color1(0), color2(0), color2(0)-color1(0))
val (sStart, sEnd, sRange) = (color1(1), color2(1), color2(1)-color1(1))
val (vStart, vEnd, vRange) = (color1(2), color2(2), color2(2)-color1(2))
if (!inverse) hRange -= signum(hRange)
((0 to nSteps).map { n => // + hDir*
new SColor(hStart + hRange*n/nSteps,
sStart + sRange*n/nSteps,
vStart + vRange*n/nSteps, true).asHEX }).toList
}
// gradient through RGB space
def gradientRGB(that: SColor, nSteps: Int = 10): List[String] = {
val (color1, color2) = (this.asRGB, that.asRGB)
val (rStart, rEnd, rRange) = (color1(0), color2(0), color2(0)-color1(0))
val (gStart, gEnd, gRange) = (color1(1), color2(1), color2(1)-color1(1))
val (bStart, bEnd, bRange) = (color1(2), color2(2), color2(2)-color1(2))
((0 to nSteps).map { n =>
new SColor(rStart + rRange*n/nSteps,
gStart + gRange*n/nSteps,
bStart + bRange*n/nSteps).asHEX }).toList
}
}
object SColorTest {
def main(args: Array[String]){
val cola = new SColor(0.3, 0.5, 0.5) // use (r: Double, g: Double, b: Double) constructor
val colb = new SColor(127, 255, 0) // use (r: Int, g: Int, b: Int) constructor
val colc = new SColor(0.9, 0.8, 0.6, true) // use (h: Double, s: Double, v: Double) constructor
val cold = new SColor(34, 125, 88, true) // use (h: Int, s: Int, v: Int) constructor
val cole = new SColor("4C7F7F") // use "HEXCODE" constructor
val colf = new SColor(8388352) // use (rgb: Int) constructor
val colg = new SColor(153,31,104) // use (r: Int, g: Int, b: Int) constructor
println(cola.asHEX)
println(colb.asDEC)
println(cole.asRGB)
println(colf.asRGB)
println(colc.asRGB)
println(colg.asHSV)
/// color gradients
println(""); (new SColor(Color.blue)).gradientRGB(new SColor(Color.red)).foreach(println)
println(""); (new SColor(Color.blue)).gradientHSV(new SColor(Color.red)).foreach(println)
println(""); (new SColor(Color.blue)).gradientHSV(new SColor(Color.red), inverse=true).foreach(println)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment