From b3e55aa7c82bbe3dcde8d9d531e124e079b13ec9 Mon Sep 17 00:00:00 2001 From: Nico Date: Wed, 17 Apr 2024 20:24:10 +0200 Subject: [PATCH] Finished scala tests --- .../io/github/scalamath/colorlib/Col1i.scala | 118 +++++++-- .../io/github/scalamath/colorlib/Col3f.scala | 30 ++- .../io/github/scalamath/colorlib/Color.scala | 162 ++---------- .../scalamath/colorlib/Col1iSuite.scala | 233 +++++++++++++++++ .../scalamath/colorlib/Col3fSuite.scala | 236 +++++++++++++++++ .../scalamath/colorlib/Col4fSuite.scala | 246 ++++++++++++++++++ 6 files changed, 855 insertions(+), 170 deletions(-) create mode 100644 src/test/scala/io/github/scalamath/colorlib/Col1iSuite.scala create mode 100644 src/test/scala/io/github/scalamath/colorlib/Col3fSuite.scala create mode 100644 src/test/scala/io/github/scalamath/colorlib/Col4fSuite.scala diff --git a/src/main/scala/io/github/scalamath/colorlib/Col1i.scala b/src/main/scala/io/github/scalamath/colorlib/Col1i.scala index c323668..4ef8aaf 100644 --- a/src/main/scala/io/github/scalamath/colorlib/Col1i.scala +++ b/src/main/scala/io/github/scalamath/colorlib/Col1i.scala @@ -129,20 +129,21 @@ case class Col1i(override val rgba: Int) extends Color { * @param a The alpha component to add in the `[0.0, 1.0]` range. * @return The sum between this color and the given components. */ - override def +(r: Float, g: Float, b: Float, a: Float): Color = this + ((r * 255.0f).round, (g * 255.0f).round, (b * 255.0f).round, (a * 255.0f).round) + override def +(r: Float, g: Float, b: Float, a: Float): Color = Col1i(this.r + r, this.g + g, this.b + b, this.a + a) /** - * Adds the given values to each component of this color and returns the result. - * - * The result is clamped. + * Adds each component of this color with the components of the given color and returns the result. * - * @param r The red component to add in the `[0, 255]` range. - * @param g The green component to add in the `[0, 255]` range. - * @param b The blue component to add in the `[0, 255]` range. - * @param a The alpha component to add in the `[0, 255]` range. - * @return The sum between this color and the given components. + * @param c The color to add. + * @return The sum between this color and the given one. */ - override def +(r: Int, g: Int, b: Int, a: Int): Color = Col1i(this.r8 + r, this.g8 + g, this.b8 + b, this.a8 + a) + override def +(c: Color): Color = { + if(c.isInstanceOf[Col1i]) { + Col1i(this.rgba + c.rgba) + } else { + Col4f(this.r + c.r, this.g + c.g, this.b + c.b, this.a + c.a) + } + } /** * Subtracts the given values from each component of this color and returns the result. @@ -155,20 +156,21 @@ case class Col1i(override val rgba: Int) extends Color { * @param a The alpha component to subtract in the `[0.0, 1.0]` range. * @return The subtraction between this color and the given components. */ - override def -(r: Float, g: Float, b: Float, a: Float): Color = this - ((r * 255.0f).round, (g * 255.0f).round, (b * 255.0f).round, (a * 255.0f).round) + override def -(r: Float, g: Float, b: Float, a: Float): Color = Col1i(this.r - r, this.g - g, this.b - b, this.a - a) /** - * Subtracts the given values from each component of this color and returns the result. - * - * The result is clamped. + * Subtracts each component of the given color from the components of this color and returns the result. * - * @param r The red component to subtract in the `[0, 255]` range. - * @param g The green component to subtract in the `[0, 255]` range. - * @param b The blue component to subtract in the `[0, 255]` range. - * @param a The alpha component to subtract in the `[0, 255]` range. - * @return The subtraction between this color and the given components. + * @param c The color to subtract. + * @return The subtraction between this color and the given one. */ - override def -(r: Int, g: Int, b: Int, a: Int): Color = Col1i(this.r8 - r, this.g8 - g, this.b8 - b, this.a8 - a) + override def -(c: Color): Color = { + if(c.isInstanceOf[Col1i]) { + Col1i(this.rgba - c.rgba) + } else { + Col4f(this.r - c.r, this.g - c.g, this.b - c.b, this.a - c.a) + } + } /** * Multiplies each component of this color with the given values and returns the result. @@ -183,6 +185,34 @@ case class Col1i(override val rgba: Int) extends Color { */ override def *(r: Float, g: Float, b: Float, a: Float): Color = Col1i((this.r8 * r).round, (this.g8 * g).round, (this.b8 * b).round, (this.a8 * a).round) + /** + * Multiplies each component of this color with each component of the given one and returns the result. + * + * @param c The color to multiply this one by. + * @return The component-wise product between this color and the given one. + */ + override def *(c: Color): Color = { + if(c.isInstanceOf[Col1i]) { + Col1i(this.r * c.r, this.g * c.g, this.b * c.b, this.a * c.a) + } else { + Col4f(this.r * c.r, this.g * c.g, this.b * c.b, this.a * c.a) + } + } + + /** + * Divides each component of this color by each component of the given one and returns the result. + * + * @param c The color to divide this one by. + * @return The component-wise division between this color and the given one. + */ + override def /(c: Color): Color = { + if(c.isInstanceOf[Col1i]) { + Col1i(this.r / c.r, this.g / c.g, this.b / c.b, this.a / c.a) + } else { + Col4f(this.r / c.r, this.g / c.g, this.b / c.b, this.a / c.a) + } + } + /** * Returns this color with its `r`, `g`, and `b` components inverted. * @@ -222,11 +252,14 @@ case class Col1i(override val rgba: Int) extends Color { if(a == 0.0f) { Col1i(0) } else { - Col1i( - (this.r * this.a * sa + c.r * c.a) / a, - (this.g * this.a * sa + c.g * c.a) / a, - (this.b * this.a * sa + c.b * c.a) / a, a - ) + val r = (this.r * this.a * sa + c.r * c.a) / a + val g = (this.g * this.a * sa + c.g * c.a) / a + val b = (this.b * this.a * sa + c.b * c.a) / a + if(c.isInstanceOf[Col1i]) { + Col1i(r, g, b, a) + } else { + Col4f(r, g, b, a) + } } } @@ -286,4 +319,39 @@ object Col1i { * @return A new color constructed from the three given components. */ def apply(r: Float, g: Float, b: Float) = new Col1i(r, g, b) + + /** + * Constructs a color from the given components in the HSV format. + * + * @param h The hue of the color. + * @param s The saturation of the color. + * @param v The lightness (value) of the color. + * @param a The alpha component of the color. + * @return A new color constructed from the given components in the HSV format. + */ + def hsv(h: Float, s: Float, v: Float, a: Float): Col1i = { + val i = (h * 6.0f).floor + val f = h * 6.0f - i + val p = v * (1.0f - s) + val q = v * (1.0f - f * s) + val t = v * (1.0f - (1.0f - f) * s) + i % 6 match { + case 0 => Col1i(v, t, p, a) + case 1 => Col1i(q, v, p, a) + case 2 => Col1i(p, v, t, a) + case 3 => Col1i(p, q, v, a) + case 4 => Col1i(t, p, v, a) + case 5 => Col1i(v, p, q, a) + } + } + + /** + * Constructs a color from the given components in the HSV format and sets the alpha component to `1.0`. + * + * @param h The hue of the color. + * @param s The saturation of the color. + * @param v The lightness (value) of the color. + * @return A new color constructed from the given components in the HSV format. + */ + def hsv(h: Float, s: Float, v: Float): Col1i = this.hsv(h, s, v, 1.0f) } \ No newline at end of file diff --git a/src/main/scala/io/github/scalamath/colorlib/Col3f.scala b/src/main/scala/io/github/scalamath/colorlib/Col3f.scala index feb9487..67180cb 100644 --- a/src/main/scala/io/github/scalamath/colorlib/Col3f.scala +++ b/src/main/scala/io/github/scalamath/colorlib/Col3f.scala @@ -144,6 +144,14 @@ case class Col3f(r: Float, g: Float, b: Float) extends Color { */ override def *(c: Color): Color = if(c.isInstanceOf[Col3f]) this * (c.r, c.g, c.b) else this * (c.r, c.g, c.b, c.a) + /** + * Multiplies each component of this color with the given value and returns the result. + * + * @param f The value to multiply this color by. + * @return The product between this color and the given value. + */ + override def *(f: Float): Color = this * (f, f, f) + /** * Divides each component of this color by each component of the given one and returns the result. * @@ -181,17 +189,35 @@ case class Col3f(r: Float, g: Float, b: Float) extends Color { */ override def lighter(k: Float): Color = Col3f(this.r + (1.0f - this.r) * k, this.g + (1.0f - this.g) * k, this.b + (1.0f - this.b) * k) + /** + * Computes the linear interpolation between this color and the given one by the given weight and returns the result. + * + * The given weight must be in the `[0.0, 1.0]` range, representing the amount of interpolation. + * + * @param to The color to interpolate to. + * @param weight The weight of the interpolation between `0.0` and `1.0`. + * @return The result of linearly interpolating between this color and the given one. + */ + override def lerp(to: Color, weight: Float): Color = { + if(to.isInstanceOf[Col3f]) { + super.lerp(to, weight) + } else { + val f = 1.0f - weight + this * (f, f, f, f) + (to * weight) + } + } + /** * Blends this color and the given one and returns the result. * - * This is the equivalent of multiplying the colors together if the given color is a [[Col3f]]. + * This returns the given color if it is a [[Col3f]], since its alpha component is always `1.0`. * * @param c The color to blend this one with. * @return The color resulting from overlaying this color over the given one. */ override def blend(c: Color): Color = { if(c.isInstanceOf[Col3f]) { - this * c + c } else { val sa = 1.0f - c.a Col4f(this.r * sa + c.r * c.a, this.g * sa + c.g * c.a, this.b * sa + c.b * c.a) diff --git a/src/main/scala/io/github/scalamath/colorlib/Color.scala b/src/main/scala/io/github/scalamath/colorlib/Color.scala index 16fcedb..24f50a4 100644 --- a/src/main/scala/io/github/scalamath/colorlib/Color.scala +++ b/src/main/scala/io/github/scalamath/colorlib/Color.scala @@ -217,52 +217,6 @@ trait Color { */ def add(r: Float, g: Float, b: Float): Color = this + (r, g, b) - /** - * Adds the given values to each component of this color and returns the result. - * - * @param r The red component to add in the `[0, 255]` range. - * @param g The green component to add in the `[0, 255]` range. - * @param b The blue component to add in the `[0, 255]` range. - * @param a The alpha component to add in the `[0, 255]` range. - * @return The sum between this color and the given components. - */ - def +(r: Int, g: Int, b: Int, a: Int): Color = this + (r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f) - - /** - * Adds the given values to each component of this color and returns the result. - * - * This method can be used in place of the `+` operator for better interoperability with Java. - * - * @param r The red component to add in the `[0, 255]` range. - * @param g The green component to add in the `[0, 255]` range. - * @param b The blue component to add in the `[0, 255]` range. - * @param a The alpha component to add in the `[0, 255]` range. - * @return The sum between this color and the given components. - */ - def add(r: Int, g: Int, b: Int, a: Int): Color = this + (r, g, b, a) - - /** - * Adds the given values to each component of this color and returns the result. - * - * @param r The red component to add in the `[0, 255]` range. - * @param g The green component to add in the `[0, 255]` range. - * @param b The blue component to add in the `[0, 255]` range. - * @return The sum between this color and the given components. - */ - def +(r: Int, g: Int, b: Int): Color = this + (r, g, b, 0) - - /** - * Adds the given values to each component of this color and returns the result. - * - * This method can be used in place of the `+` operator for better interoperability with Java. - * - * @param r The red component to add in the `[0, 255]` range. - * @param g The green component to add in the `[0, 255]` range. - * @param b The blue component to add in the `[0, 255]` range. - * @return The sum between this color and the given components. - */ - def add(r: Int, g: Int, b: Int): Color = this + (r, g, b) - /** * Subtracts the given values from each component of this color and returns the result. * @@ -327,52 +281,6 @@ trait Color { */ def subtract(r: Float, g: Float, b: Float): Color = this - (r, g, b) - /** - * Subtracts the given values from each component of this color and returns the result. - * - * @param r The red component to subtract in the `[0, 255]` range. - * @param g The green component to subtract in the `[0, 255]` range. - * @param b The blue component to subtract in the `[0, 255]` range. - * @param a The alpha component to subtract in the `[0, 255]` range. - * @return The subtraction between this color and the given components. - */ - def -(r: Int, g: Int, b: Int, a: Int): Color = this - (r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f) - - /** - * Subtracts the given values from each component of this color and returns the result. - * - * This method can be used in place of the `-` operator for better interoperability with Java. - * - * @param r The red component to subtract in the `[0, 255]` range. - * @param g The green component to subtract in the `[0, 255]` range. - * @param b The blue component to subtract in the `[0, 255]` range. - * @param a The alpha component to subtract in the `[0, 255]` range. - * @return The subtraction between this color and the given components. - */ - def subtract(r: Int, g: Int, b: Int, a: Int): Color = this - (r, g, b, a) - - /** - * Subtracts the given values from each component of this color and returns the result. - * - * @param r The red component to subtract in the `[0, 255]` range. - * @param g The green component to subtract in the `[0, 255]` range. - * @param b The blue component to subtract in the `[0, 255]` range. - * @return The subtraction between this color and the given components. - */ - def -(r: Int, g: Int, b: Int): Color = this - (r, g, b, 0) - - /** - * Subtracts the given values from each component of this color and returns the result. - * - * This method can be used in place of the `-` operator for better interoperability with Java. - * - * @param r The red component to subtract in the `[0, 255]` range. - * @param g The green component to subtract in the `[0, 255]` range. - * @param b The blue component to subtract in the `[0, 255]` range. - * @return The subtraction between this color and the given components. - */ - def subtract(r: Int, g: Int, b: Int): Color = this - (r, g, b) - /** * Multiplies each component of this color with the given values and returns the result. * @@ -536,6 +444,15 @@ trait Color { */ def inverted: Color + /** + * Returns the perceived brightness of this color using the RGB to Luma conversion formula. + * + * Colors with a luminance smaller than `0.5` can be generally considered dark. + * + * @return The luminance of this color. + */ + def luminance: Float = 0.2126f * this.r + 0.7152f * this.g + 0.0722f * this.b + /** * Makes this color darker by the given amount and returns the result. * @@ -563,16 +480,7 @@ trait Color { * @param weight The weight of the interpolation between `0.0` and `1.0`. * @return The result of linearly interpolating between this color and the given one. */ - def lerp(to: Color, weight: Float): Color = (this * (1.0f - weight)) + (to * weight) - - /** - * Returns the perceived brightness of this color using the RGB to Luma conversion formula. - * - * Colors with a luminance smaller than `0.5` can be generally considered dark. - * - * @return The luminance of this color. - */ - def luminance: Float = 0.2126f * this.r + 0.7152f * this.g + 0.0722f * this.b + def lerp(to: Color, weight: Float): Color = this * (1.0f - weight) + (to * weight) /** * Checks if the components of this color are approximately equal to the given ones using an internal epsilon. @@ -645,54 +553,22 @@ trait Color { def equalsApprox(c: Color): Boolean = this ~= c /** - * Checks if the components of this color are equal to the given ones. + * Checks if this color is approximately equal to the given one in the RGBA format. * - * @param r The red component of the color in the `[0, 255]` range. - * @param g The green component of the color in the `[0, 255]` range. - * @param b The blue component of the color in the `[0, 255]` range. - * @return True if the components of this color are equal to the given ones, otherwise false. - * @see [[r8]], [[g8]], [[b8]] + * @param rgba The value in the RGBA format to compare this color to. + * @return True if this color is approximately equal to the given one, otherwise false. */ - def ==(r: Int, g: Int, b: Int): Boolean = this.r8 == r && this.g8 == g && this.b8 == b + def ~=(rgba: Int): Boolean = this ~= (((rgba >> 24) & 0xff) / 255.0f, ((rgba >> 16) & 0xff) / 255.0f, ((rgba >> 8) & 0xff) / 255.0f, (rgba & 0xff) / 255.0f) /** - * Checks if the components of this color are equal to the given ones. + * Checks if this color is approximately equal to the given one in the RGBA format. * - * This method can be used in place of the `==` operator for better interoperability with Java. - * - * @param r The red component of the color in the `[0, 255]` range. - * @param g The green component of the color in the `[0, 255]` range. - * @param b The blue component of the color in the `[0, 255]` range. - * @return True if the components of this color are equal to the given ones, otherwise false. - * @see [[r8]], [[g8]], [[b8]] - */ - def equals(r: Int, g: Int, b: Int): Boolean = this == (r, g, b) - - /** - * Checks if the components of this color are equal to the given ones. - * - * @param r The red component of the color in the `[0, 255]` range. - * @param g The green component of the color in the `[0, 255]` range. - * @param b The blue component of the color in the `[0, 255]` range. - * @param a The alpha component of the color in the `[0, 255]` range. - * @return True if the components of this color are equal to the given ones, otherwise false. - * @see [[r8]], [[g8]], [[b8]], [[a8]] - */ - def ==(r: Int, g: Int, b: Int, a: Int): Boolean = this.r8 == r && this.g8 == g && this.b8 == b && this.a8 == a - - /** - * Checks if the components of this color are equal to the given ones. - * - * This method can be used in place of the `==` operator for better interoperability with Java. + * This method can be used in place of the `~=` operator for better interoperability with Java. * - * @param r The red component of the color in the `[0, 255]` range. - * @param g The green component of the color in the `[0, 255]` range. - * @param b The blue component of the color in the `[0, 255]` range. - * @param a The alpha component of the color in the `[0, 255]` range. - * @return True if the components of this color are equal to the given ones, otherwise false. - * @see [[r8]], [[g8]], [[b8]], [[a8]] + * @param rgba The value in the RGBA format to compare this color to. + * @return True if this color is approximately equal to the given one, otherwise false. */ - def equals(r: Int, g: Int, b: Int, a: Int): Boolean = this == (r, g, b, a) + def equalsApprox(rgba: Int): Boolean = this ~= rgba } /** diff --git a/src/test/scala/io/github/scalamath/colorlib/Col1iSuite.scala b/src/test/scala/io/github/scalamath/colorlib/Col1iSuite.scala new file mode 100644 index 0000000..5f12bed --- /dev/null +++ b/src/test/scala/io/github/scalamath/colorlib/Col1iSuite.scala @@ -0,0 +1,233 @@ +package io.github.scalamath.colorlib + +import io.github.scalamath +import org.scalactic.Tolerance.convertNumericToPlusOrMinusWrapper +import org.scalactic.{Equality, TolerantNumerics} +import org.scalatest.funsuite.AnyFunSuite + +class Col1iSuite extends AnyFunSuite { + + implicit val floatEquality: Equality[Float] = TolerantNumerics.tolerantFloatEquality(scalamath.Epsilon.toFloat) + + implicit val colorEquality: Equality[Color] = (a: Color, b: Any) => b match { + case b: Color => a.getClass == b.getClass && (a.r - b.r).abs < 0.01f && (a.g - b.g).abs < 0.01f && (a.b - b.b).abs < 0.01f && (a.a - b.a).abs < 0.01f + case _ => false + } + + test("Create color from rgba floats and retrieve the values") { + val color = Col1i(0.372549f, 0.619608f, 0.627451f, 0.8f) + assert(color.r === 0.372549f) + assert(color.g === 0.619608f) + assert(color.b === 0.627451f) + assert(color.a === 0.8f) + } + + test("Create color from rgb floats and retrieve the values") { + val color = Col1i(0.372549f, 0.619608f, 0.627451f) + assert(color.r === 0.372549f) + assert(color.g === 0.619608f) + assert(color.b === 0.627451f) + assert(color.a === 1.0f) + } + + test("Create color from rgba integers and retrieve the values") { + val color = Col1i(95, 158, 160, 128) + assert(color.r8 == 95) + assert(color.g8 == 158) + assert(color.b8 == 160) + assert(color.a8 == 128) + } + + test("Create color from rgb integers and retrieve the values") { + val color = Col1i(95, 158, 160) + assert(color.r8 == 95) + assert(color.g8 == 158) + assert(color.b8 == 160) + assert(color.a8 == 255) + } + + test("Create color from HSV and retrieve the values") { + val color = Col1i.hsv(0.5f, 0.41f, 0.63f) + assert(color.h === 0.5f +- 0.01f) + assert(color.s === 0.41f +- 0.01f) + assert(color.v === 0.63f +- 0.01f) + } + + test("Create color from rgba hex and retrieve the value") { + val color = Col1i(0x5f9ea0ff) + assert(color.rgba == 0x5f9ea0ff) + } + + test("Create color from rgba hex and retrieve the argb value") { + val color = Col1i(0x5f9ea0ff) + assert(color.argb == 0xff5f9ea0) + } + + test("Add four floats to a color") { + val color = Col1i(0.3f, 0.4f, 0.5f, 0.6f) + val res = Col1i(0.4f, 0.6f, 0.8f, 1.0f) + assert(color + (0.1f, 0.2f, 0.3f, 0.4f) === res) + } + + test("Add three floats to a color") { + val color = Col1i(0.3f, 0.4f, 0.5f, 0.8f) + val res = Col1i(0.4f, 0.6f, 0.8f, 0.8f) + assert(color + (0.1f, 0.2f, 0.3f) === res) + } + + test("Sum of two colors") { + val c1 = Col1i(0.3f, 0.4f, 0.5f, 0.6f) + val c2 = Col1i(0.1f, 0.2f, 0.3f, 0.4f) + val res = Col1i(0.4f, 0.6f, 0.8f, 1.0f) + assert(c1 + c2 === res) + } + + test("Sum of two colors of different type") { + val c1 = Col1i(0.3f, 0.4f, 0.5f, 0.6f) + val c2 = Col4f(0.1f, 0.2f, 0.3f, 0.4f) + val res = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + assert(c1 + c2 === res) + } + + test("Subtract four floats from a color") { + val color = Col1i(0.4f, 0.6f, 0.8f, 1.0f) + val res = Col1i(0.3f, 0.4f, 0.5f, 0.6f) + assert(color - (0.1f, 0.2f, 0.3f, 0.4f) === res) + } + + test("Subtract three floats from a color") { + val color = Col1i(0.4f, 0.6f, 0.8f, 1.0f) + val res = Col1i(0.3f, 0.4f, 0.5f, 1.0f) + assert(color - (0.1f, 0.2f, 0.3f) === res) + } + + test("Subtract two colors") { + val c1 = Col1i(0.4f, 0.6f, 0.8f, 1.0f) + val c2 = Col1i(0.3f, 0.4f, 0.5f, 0.6f) + val res = Col1i(0.1f, 0.2f, 0.3f, 0.4f) + assert(c1 - c2 === res) + } + + test("Subtract two colors of different types") { + val c1 = Col1i(0.4f, 0.6f, 0.8f, 1.0f) + val c2 = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + val res = Col4f(0.1f, 0.2f, 0.3f, 0.4f) + assert(c1 - c2 === res) + } + + test("Multiply a color with four floats") { + val color = Col1i(0.2f, 0.3f, 0.4f, 0.5f) + val res = Col1i(0.4f, 0.9f, 0.6f, 1.0f) + assert(color * (2.0f, 3.0f, 1.5f, 2.0f) === res) + } + + test("Multiply a color with three floats") { + val color = Col1i(0.2f, 0.3f, 0.4f, 1.0f) + val res = Col1i(0.4f, 0.9f, 0.6f) + assert(color * (2.0f, 3.0f, 1.5f) === res) + } + + test("Multiply two colors") { + val c1 = Col1i(0.5f, 0.4f, 0.6f, 1.0f) + val c2 = Col1i(0.2f, 0.4f, 0.6f, 1.0f) + val res = Col1i(0.1f, 0.16f, 0.36f, 1.0f) + assert(c1 * c2 === res) + } + + test("Multiply two colors of different types") { + val c1 = Col1i(0.2f, 0.3f, 0.4f, 1.0f) + val c2 = Col4f(2.0f, 3.0f, 1.5f, 1.0f) + val res = Col4f(0.4f, 0.9f, 0.6f, 1.0f) + assert(c1 * c2 === res) + } + + test("Multiply a color with a float") { + val color = Col1i(0.2f, 0.4f, 0.6f, 1.0f) + val res = Col1i(0.3f, 0.6f, 0.9f, 1.0f) + assert(color * 1.5f === res) + } + + test("Divide a color by four floats") { + val color = Col1i(1.0f, 0.8f, 0.6f, 1.0f) + val res = Col1i(0.5f, 0.2f, 0.2f, 0.2f) + assert(color / (2.0f, 4.0f, 3.0f, 5.0f) === res) + } + + test("Divide a color by three floats") { + val color = Col1i(1.0f, 0.8f, 0.6f, 1.0f) + val res = Col1i(0.5f, 0.2f, 0.2f, 1.0f) + assert(color / (2.0f, 4.0f, 3.0f) === res) + } + + test("Divide two colors") { + val c1 = Col1i(0.1f, 0.2f, 0.1f, 0.4f) + val c2 = Col1i(0.2f, 0.2f, 0.25f, 0.5f) + val res = Col1i(0.5f, 1.0f, 0.4f, 0.8f) + assert(c1 / c2 === res) + } + + test("Divide two colors of different types") { + val c1 = Col1i(1.0f, 0.8f, 0.6f, 1.0f) + val c2 = Col4f(0.5f, 0.2f, 0.25f, 1.0f) + val res = Col4f(2.0f, 4.0f, 2.4f, 1.0f) + assert(c1 / c2 === res) + } + + test("Blend two colors") { + val c1 = Col1i(1.0f, 0.0f, 0.0f, 1.0f) + val c2 = Col1i(1.0f, 1.0f, 0.0f, 0.5f) + val res = Col1i(1.0f, 0.5f, 0.0f, 1.0f) + assert(c1.blend(c2) === res) + } + + test("Blend two colors of different type") { + val c1 = Col1i(1.0f, 0.0f, 0.0f, 1.0f) + val c2 = Col4f(1.0f, 1.0f, 0.0f, 0.5f) + val res = Col4f(1.0f, 0.5f, 0.0f, 1.0f) + assert(c1.blend(c2) === res) + } + + test("Invert a color") { + val color = Col1i(0.2f, 0.5f, 0.7f, 1.0f) + val res = Col1i(0.8f, 0.5f, 0.3f, 1.0f) + assert(color.inverted === res) + } + + test("Color luminance") { + val color = Col1i(0.372549f, 0.619608f, 0.627451f) + val res = 0.2126f * 0.372549f + 0.7152f * 0.619608f + 0.0722f * 0.627451f + assert(color.luminance === res) + } + + test("Make a color darker") { + val color = Col1i(0.372549f, 0.619608f, 0.627451f) + assert(color.darker(0.1f).luminance < color.luminance) + } + + test("Make a color lighter") { + val color = Col1i(0.372549f, 0.619608f, 0.627451f) + assert(color.lighter(0.1f).luminance > color.luminance) + } + + test("Linear interpolation between two colors") { + val c1 = Col1i(0.2f, 0.4f, 0.2f, 0.5f) + val c2 = Col1i(0.4f, 0.8f, 0.6f, 1.0f) + val res1 = Col1i(0.3f, 0.6f, 0.4f, 0.75f) + val res2 = Col1i(0.25f, 0.5f, 0.3f, 0.625f) + val res3 = Col1i(0.35f, 0.7f, 0.5f, 0.875f) + assert(c1.lerp(c2, 0.5f) === res1) + assert(c1.lerp(c2, 0.25f) === res2) + assert(c1.lerp(c2, 0.75f) === res3) + } + + test("Linear interpolation between two colors of different types") { + val c1 = Col1i(0.2f, 0.4f, 0.2f, 0.5f) + val c2 = Col4f(0.4f, 0.8f, 0.6f, 1.0f) + val res1 = Col4f(0.3f, 0.6f, 0.4f, 0.75f) + val res2 = Col4f(0.25f, 0.5f, 0.3f, 0.625f) + val res3 = Col4f(0.35f, 0.7f, 0.5f, 0.875f) + assert(c1.lerp(c2, 0.5f) === res1) + assert(c1.lerp(c2, 0.25f) === res2) + assert(c1.lerp(c2, 0.75f) === res3) + } +} diff --git a/src/test/scala/io/github/scalamath/colorlib/Col3fSuite.scala b/src/test/scala/io/github/scalamath/colorlib/Col3fSuite.scala new file mode 100644 index 0000000..2ebf4b9 --- /dev/null +++ b/src/test/scala/io/github/scalamath/colorlib/Col3fSuite.scala @@ -0,0 +1,236 @@ +package io.github.scalamath.colorlib + +import io.github.scalamath +import org.scalactic.{Equality, TolerantNumerics} +import org.scalatest.funsuite.AnyFunSuite + +class Col3fSuite extends AnyFunSuite { + + implicit val floatEquality: Equality[Float] = TolerantNumerics.tolerantFloatEquality(scalamath.Epsilon.toFloat) + + implicit val colorEquality: Equality[Color] = (a: Color, b: Any) => b match { + case b: Color => a.getClass == b.getClass && (a.r - b.r).abs < 0.01f && (a.g - b.g).abs < 0.01f && (a.b - b.b).abs < 0.01f && (a.a - b.a).abs < 0.01f + case _ => false + } + + test("Create color from rgb floats and retrieve the values") { + val color = Col3f(0.372549f, 0.619608f, 0.627451f) + assert(color.r === 0.372549f) + assert(color.g === 0.619608f) + assert(color.b === 0.627451f) + assert(color.a === 1.0f) + } + + test("Create color from rgb integers and retrieve the values") { + val color = Col3f(95, 158, 160) + assert(color.r8 == 95) + assert(color.g8 == 158) + assert(color.b8 == 160) + assert(color.a8 == 255) + } + + test("Create color from HSV and retrieve the values") { + val color = Col3f.hsv(0.5f, 0.41f, 0.63f) + assert(color.h === 0.5f) + assert(color.s === 0.41f) + assert(color.v === 0.63f) + } + + test("Create color from rgb hex and retrieve the rgba value") { + val color = Col3f(0x5f9ea0) + assert(color.rgba == 0x5f9ea0ff) + } + + test("Create color from rgb hex and retrieve the argb value") { + val color = Col3f(0x5f9ea0) + assert(color.argb == 0xff5f9ea0) + } + + test("Add three floats to a color") { + val color = Col3f(0.3f, 0.4f, 0.5f) + val res = Col3f(0.4f, 0.6f, 0.8f) + assert(color + (0.1f, 0.2f, 0.3f) === res) + } + + test("Add four floats to a color") { + val color = Col3f(0.3f, 0.4f, 0.5f) + val res = Col4f(0.4f, 0.6f, 0.8f, 1.4f) + assert(color + (0.1f, 0.2f, 0.3f, 0.4f) === res) + } + + test("Sum of two colors") { + val c1 = Col3f(0.3f, 0.4f, 0.5f) + val c2 = Col3f(0.1f, 0.2f, 0.3f) + val res = Col3f(0.4f, 0.6f, 0.8f) + assert(c1 + c2 === res) + } + + test("Sum of two colors of different type") { + val c1 = Col3f(0.3f, 0.4f, 0.5f) + val c2 = Col4f(0.1f, 0.2f, 0.3f, 0.4f) + val res = Col4f(0.4f, 0.6f, 0.8f, 1.4f) + assert(c1 + c2 === res) + } + + test("Subtract three floats from a color") { + val color = Col3f(0.4f, 0.6f, 0.8f) + val res = Col3f(0.3f, 0.4f, 0.5f) + assert(color - (0.1f, 0.2f, 0.3f) === res) + } + + test("Subtract four floats from a color") { + val color = Col3f(0.4f, 0.6f, 0.8f) + val res = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + assert(color - (0.1f, 0.2f, 0.3f, 0.4f) === res) + } + + test("Subtract two colors") { + val c1 = Col3f(0.4f, 0.6f, 0.8f) + val c2 = Col3f(0.3f, 0.4f, 0.5f) + val res = Col3f(0.1f, 0.2f, 0.3f) + assert(c1 - c2 === res) + } + + test("Subtract two colors of different types") { + val c1 = Col3f(0.4f, 0.6f, 0.8f) + val c2 = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + val res = Col4f(0.1f, 0.2f, 0.3f, 0.4f) + assert(c1 - c2 === res) + } + + test("Multiply a color with three floats") { + val color = Col3f(0.2f, 0.3f, 0.4f) + val res = Col3f(0.4f, 0.9f, 0.6f) + assert(color * (2.0f, 3.0f, 1.5f) === res) + } + + test("Multiply a color with four floats") { + val color = Col3f(0.2f, 0.3f, 0.4f) + val res = Col4f(0.4f, 0.9f, 0.6f, 0.8f) + assert(color * (2.0f, 3.0f, 1.5f, 0.8f) === res) + } + + test("Multiply two colors") { + val c1 = Col3f(2.0f, 3.0f, 1.5f) + val c2 = Col3f(0.2f, 0.3f, 0.4f) + val res = Col3f(0.4f, 0.9f, 0.6f) + assert(c1 * c2 === res) + } + + test("Multiply two colors of different types") { + val c1 = Col3f(2.0f, 3.0f, 1.5f) + val c2 = Col4f(0.2f, 0.3f, 0.4f, 0.8f) + val res = Col4f(0.4f, 0.9f, 0.6f, 0.8f) + assert(c1 * c2 === res) + } + + test("Multiply a color with a float") { + val color = Col3f(0.2f, 0.4f, 0.6f) + val res = Col3f(0.3f, 0.6f, 0.9f) + assert(color * 1.5f === res) + } + + test("Divide a color by three floats") { + val color = Col3f(1.0f, 0.8f, 0.6f) + val res = Col3f(0.5f, 0.2f, 0.2f) + assert(color / (2.0f, 4.0f, 3.0f) === res) + } + + test("Divide a color by four floats") { + val color = Col3f(1.0f, 0.8f, 0.6f) + val res = Col4f(0.5f, 0.2f, 0.2f, 0.2f) + assert(color / (2.0f, 4.0f, 3.0f, 5.0f) === res) + } + + test("Divide two colors") { + val c1 = Col3f(1.0f, 0.8f, 0.6f) + val c2 = Col3f(2.0f, 4.0f, 3.0f) + val res = Col3f(0.5f, 0.2f, 0.2f) + assert(c1 / c2 === res) + } + + test("Divide two colors of different types") { + val c1 = Col3f(1.0f, 0.8f, 0.6f) + val c2 = Col4f(0.5f, 0.2f, 0.25f, 1.0f) + val res = Col4f(2.0f, 4.0f, 2.4f, 1.0f) + assert(c1 / c2 === res) + } + + test("Blend two colors") { + val c1 = Col3f(1.0f, 0.0f, 0.0f) + val c2 = Col3f(1.0f, 1.0f, 0.0f) + assert(c1.blend(c2) === c2) + } + + test("Blend two colors of different type") { + val c1 = Col3f(1.0f, 0.0f, 0.0f) + val c2 = Col4f(1.0f, 1.0f, 0.0f, 0.5f) + val res = Col4f(1.0f, 0.5f, 0.0f, 1.0f) + assert(c1.blend(c2) === res) + } + + test("Invert a color") { + val color = Col3f(0.2f, 0.5f, 0.7f) + val res = Col3f(0.8f, 0.5f, 0.3f) + assert(color.inverted === res) + } + + test("Color luminance") { + val color = Col3f(0.372549f, 0.619608f, 0.627451f) + val res = 0.2126f * 0.372549f + 0.7152f * 0.619608f + 0.0722f * 0.627451f + assert(color.luminance === res) + } + + test("Make a color darker") { + val color = Col3f(0.372549f, 0.619608f, 0.627451f) + assert(color.darker(0.1f).luminance < color.luminance) + } + + test("Make a color lighter") { + val color = Col3f(0.372549f, 0.619608f, 0.627451f) + assert(color.lighter(0.1f).luminance > color.luminance) + } + + test("Linear interpolation between two colors") { + val c1 = Col3f(0.2f, 0.4f, 0.2f) + val c2 = Col3f(0.4f, 0.8f, 0.6f) + val res1 = Col3f(0.3f, 0.6f, 0.4f) + val res2 = Col3f(0.25f, 0.5f, 0.3f) + val res3 = Col3f(0.35f, 0.7f, 0.5f) + assert(c1.lerp(c2, 0.5f) === res1) + assert(c1.lerp(c2, 0.25f) === res2) + assert(c1.lerp(c2, 0.75f) === res3) + } + + test("Linear interpolation between two colors of different types") { + val c1 = Col3f(0.2f, 0.4f, 0.2f) + val c2 = Col4f(0.4f, 0.8f, 0.6f, 0.5f) + val res1 = Col4f(0.3f, 0.6f, 0.4f, 0.75f) + val res2 = Col4f(0.25f, 0.5f, 0.3f, 0.875f) + val res3 = Col4f(0.35f, 0.7f, 0.5f, 0.625f) + assert(c1.lerp(c2, 0.5f) === res1) + assert(c1.lerp(c2, 0.25f) === res2) + assert(c1.lerp(c2, 0.75f) === res3) + } + + test("Color equals approx three floats") { + val color = Col3f(0.9999999f, 0.4999999f, 0.7500001f) + assert(color ~= (1.0f, 0.5f, 0.75f)) + } + + test("Color equals approx four floats") { + val color = Col3f(0.9999999f, 0.4999999f, 0.7500001f) + assert(color ~= (1.0f, 0.5f, 0.75f, 1.0000001f)) + } + + test("Color equals approx another color") { + val c1 = Col3f(0.9999999f, 0.4999999f, 0.7500001f) + val c2 = Col3f(1.0f, 0.5f, 0.75f) + assert(c1 ~= c2) + } + + test("Color equals approx rgba value") { + val color = Col3f(0.372549f, 0.619608f, 0.627451f) + assert(color ~= 0x5f9ea0ff) + } +} diff --git a/src/test/scala/io/github/scalamath/colorlib/Col4fSuite.scala b/src/test/scala/io/github/scalamath/colorlib/Col4fSuite.scala new file mode 100644 index 0000000..43152c3 --- /dev/null +++ b/src/test/scala/io/github/scalamath/colorlib/Col4fSuite.scala @@ -0,0 +1,246 @@ +package io.github.scalamath.colorlib + +import io.github.scalamath +import org.scalactic.{Equality, TolerantNumerics} +import org.scalatest.funsuite.AnyFunSuite + +class Col4fSuite extends AnyFunSuite { + + implicit val floatEquality: Equality[Float] = TolerantNumerics.tolerantFloatEquality(scalamath.Epsilon.toFloat) + + implicit val colorEquality: Equality[Color] = (a: Color, b: Any) => b match { + case b: Color => a.getClass == b.getClass && (a.r - b.r).abs < 0.01f && (a.g - b.g).abs < 0.01f && (a.b - b.b).abs < 0.01f && (a.a - b.a).abs < 0.01f + case _ => false + } + + test("Create color from rgba floats and retrieve the values") { + val color = Col4f(0.372549f, 0.619608f, 0.627451f, 0.8f) + assert(color.r === 0.372549f) + assert(color.g === 0.619608f) + assert(color.b === 0.627451f) + assert(color.a === 0.8f) + } + + test("Create color from rgb floats and retrieve the values") { + val color = Col4f(0.372549f, 0.619608f, 0.627451f) + assert(color.r === 0.372549f) + assert(color.g === 0.619608f) + assert(color.b === 0.627451f) + assert(color.a === 1.0f) + } + + test("Create color from rgba integers and retrieve the values") { + val color = Col4f(95, 158, 160, 128) + assert(color.r8 == 95) + assert(color.g8 == 158) + assert(color.b8 == 160) + assert(color.a8 == 128) + } + + test("Create color from rgb integers and retrieve the values") { + val color = Col4f(95, 158, 160) + assert(color.r8 == 95) + assert(color.g8 == 158) + assert(color.b8 == 160) + assert(color.a8 == 255) + } + + test("Create color from HSV and retrieve the values") { + val color = Col4f.hsv(0.5f, 0.41f, 0.63f) + assert(color.h === 0.5f) + assert(color.s === 0.41f) + assert(color.v === 0.63f) + } + + test("Create color from rgba hex and retrieve the value") { + val color = Col4f.rgba(0x5f9ea0ff) + assert(color.rgba == 0x5f9ea0ff) + } + + test("Create color from argb hex and retrieve the value") { + val color = Col4f.argb(0xff5f9ea0) + assert(color.argb == 0xff5f9ea0) + } + + test("Add four floats to a color") { + val color = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + val res = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + assert(color + (0.1f, 0.2f, 0.3f, 0.4f) === res) + } + + test("Add three floats to a color") { + val color = Col4f(0.3f, 0.4f, 0.5f, 0.8f) + val res = Col4f(0.4f, 0.6f, 0.8f, 0.8f) + assert(color + (0.1f, 0.2f, 0.3f) === res) + } + + test("Sum of two colors") { + val c1 = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + val c2 = Col4f(0.1f, 0.2f, 0.3f, 0.4f) + val res = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + assert(c1 + c2 === res) + } + + test("Sum of two colors of different type") { + val c1 = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + val c2 = Col1i(0.1f, 0.2f, 0.3f, 0.4f) + val res = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + assert(c1 + c2 === res) + } + + test("Subtract four floats from a color") { + val color = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + val res = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + assert(color - (0.1f, 0.2f, 0.3f, 0.4f) === res) + } + + test("Subtract three floats from a color") { + val color = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + val res = Col4f(0.3f, 0.4f, 0.5f, 1.0f) + assert(color - (0.1f, 0.2f, 0.3f) === res) + } + + test("Subtract two colors") { + val c1 = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + val c2 = Col4f(0.3f, 0.4f, 0.5f, 0.6f) + val res = Col4f(0.1f, 0.2f, 0.3f, 0.4f) + assert(c1 - c2 === res) + } + + test("Subtract two colors of different types") { + val c1 = Col4f(0.4f, 0.6f, 0.8f, 1.0f) + val c2 = Col1i(0.3f, 0.4f, 0.5f, 0.6f) + val res = Col4f(0.1f, 0.2f, 0.3f, 0.4f) + assert(c1 - c2 === res) + } + + test("Multiply a color with four floats") { + val color = Col4f(0.2f, 0.3f, 0.4f, 0.5f) + val res = Col4f(0.4f, 0.9f, 0.6f, 1.0f) + assert(color * (2.0f, 3.0f, 1.5f, 2.0f) === res) + } + + test("Multiply a color with three floats") { + val color = Col4f(0.2f, 0.3f, 0.4f, 1.0f) + val res = Col4f(0.4f, 0.9f, 0.6f) + assert(color * (2.0f, 3.0f, 1.5f) === res) + } + + test("Multiply two colors") { + val c1 = Col4f(2.0f, 3.0f, 1.5f, 1.0f) + val c2 = Col4f(0.2f, 0.3f, 0.4f, 1.0f) + val res = Col4f(0.4f, 0.9f, 0.6f, 1.0f) + assert(c1 * c2 === res) + } + + test("Multiply two colors of different types") { + val c1 = Col4f(2.0f, 3.0f, 1.5f, 1.0f) + val c2 = Col1i(0.2f, 0.3f, 0.4f, 1.0f) + val res = Col4f(0.4f, 0.9f, 0.6f, 1.0f) + assert(c1 * c2 === res) + } + + test("Multiply a color with a float") { + val color = Col4f(0.2f, 0.4f, 0.6f, 1.0f) + val res = Col4f(0.3f, 0.6f, 0.9f, 1.5f) + assert(color * 1.5f === res) + } + + test("Divide a color by four floats") { + val color = Col4f(1.0f, 0.8f, 0.6f, 1.0f) + val res = Col4f(0.5f, 0.2f, 0.2f, 0.2f) + assert(color / (2.0f, 4.0f, 3.0f, 5.0f) === res) + } + + test("Divide a color by three floats") { + val color = Col4f(1.0f, 0.8f, 0.6f, 1.0f) + val res = Col4f(0.5f, 0.2f, 0.2f, 1.0f) + assert(color / (2.0f, 4.0f, 3.0f) === res) + } + + test("Divide two colors") { + val c1 = Col4f(1.0f, 0.8f, 0.6f, 1.0f) + val c2 = Col4f(2.0f, 4.0f, 3.0f, 5.0f) + val res = Col4f(0.5f, 0.2f, 0.2f, 0.2f) + assert(c1 / c2 === res) + } + + test("Divide two colors of different types") { + val c1 = Col4f(1.0f, 0.8f, 0.6f, 1.0f) + val c2 = Col1i(0.5f, 0.2f, 0.25f, 1.0f) + val res = Col4f(2.0f, 4.0f, 2.4f, 1.0f) + assert(c1 / c2 === res) + } + + test("Blend two colors") { + val c1 = Col4f(1.0f, 0.0f, 0.0f, 1.0f) + val c2 = Col4f(1.0f, 1.0f, 0.0f, 0.5f) + val res = Col4f(1.0f, 0.5f, 0.0f, 1.0f) + assert(c1.blend(c2) === res) + } + + test("Blend two colors of different type") { + val c1 = Col4f(1.0f, 0.0f, 0.0f, 1.0f) + val c2 = Col1i(1.0f, 1.0f, 0.0f, 0.5f) + val res = Col4f(1.0f, 0.5f, 0.0f, 1.0f) + assert(c1.blend(c2) === res) + } + + test("Invert a color") { + val color = Col4f(0.2f, 0.5f, 0.7f, 1.0f) + val res = Col4f(0.8f, 0.5f, 0.3f, 1.0f) + assert(color.inverted === res) + } + + test("Color luminance") { + val color = Col4f(0.372549f, 0.619608f, 0.627451f) + val res = 0.2126f * 0.372549f + 0.7152f * 0.619608f + 0.0722f * 0.627451f + assert(color.luminance === res) + } + + test("Make a color darker") { + val color = Col4f(0.372549f, 0.619608f, 0.627451f) + assert(color.darker(0.1f).luminance < color.luminance) + } + + test("Make a color lighter") { + val color = Col4f(0.372549f, 0.619608f, 0.627451f) + assert(color.lighter(0.1f).luminance > color.luminance) + } + + test("Linear interpolation between two colors") { + val c1 = Col4f(0.2f, 0.4f, 0.2f, 0.5f) + val c2 = Col4f(0.4f, 0.8f, 0.6f, 1.0f) + val res1 = Col4f(0.3f, 0.6f, 0.4f, 0.75f) + val res2 = Col4f(0.25f, 0.5f, 0.3f, 0.625f) + val res3 = Col4f(0.35f, 0.7f, 0.5f, 0.875f) + assert(c1.lerp(c2, 0.5f) === res1) + assert(c1.lerp(c2, 0.25f) === res2) + assert(c1.lerp(c2, 0.75f) === res3) + } + + test("Linear interpolation between two colors of different types") { + // TODO + } + + test("Color equals approx three floats") { + val color = Col4f(0.9999999f, 0.4999999f, 0.7500001f) + assert(color ~= (1.0f, 0.5f, 0.75f)) + } + + test("Color equals approx four floats") { + val color = Col4f(0.9999999f, 0.4999999f, 0.7500001f, 0.9999999f) + assert(color ~= (1.0f, 0.5f, 0.75f, 1.0f)) + } + + test("Color equals approx another color") { + val c1 = Col4f(0.9999999f, 0.4999999f, 0.7500001f, 0.9999999f) + val c2 = Col4f(1.0f, 0.5f, 0.75f, 1.0f) + assert(c1 ~= c2) + } + + test("Color equals approx rgba value") { + val color = Col4f(0.372549f, 0.619608f, 0.627451f) + assert(color ~= 0x5f9ea0ff) + } +}