Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[wpilib] Add hex string constructor to Color and Color8Bit #5063

Merged
merged 12 commits into from
Dec 1, 2023
2 changes: 0 additions & 2 deletions wpilibc/src/main/native/cpp/util/Color.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

#include "frc/util/Color.h"

#include <fmt/format.h>

using namespace frc;

std::string Color::HexString() const {
Expand Down
2 changes: 0 additions & 2 deletions wpilibc/src/main/native/cpp/util/Color8Bit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

#include "frc/util/Color8Bit.h"

#include <fmt/format.h>

using namespace frc;

std::string Color8Bit::HexString() const {
Expand Down
35 changes: 35 additions & 0 deletions wpilibc/src/main/native/include/frc/util/Color.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
#pragma once

#include <algorithm>
#include <stdexcept>
#include <string>
#include <string_view>

#include <fmt/core.h>
#include <wpi/StringExtras.h>

namespace frc {

Expand Down Expand Up @@ -739,6 +744,9 @@ class Color {
*/
static const Color kYellowGreen;

/**
* Constructs a default color (black).
*/
constexpr Color() = default;

/**
Expand All @@ -763,6 +771,33 @@ class Color {
constexpr Color(int r, int g, int b)
: Color(r / 255.0, g / 255.0, b / 255.0) {}

/**
* Constructs a Color from a hex string.
*
* @param hexString a string of the format <tt>\#RRGGBB</tt>
* @throws std::invalid_argument if the hex string is invalid.
*/
explicit constexpr Color(std::string_view hexString) {
if (hexString.length() != 7 || !hexString.starts_with("#") ||
!wpi::isHexDigit(hexString[1]) || !wpi::isHexDigit(hexString[2]) ||
!wpi::isHexDigit(hexString[3]) || !wpi::isHexDigit(hexString[4]) ||
!wpi::isHexDigit(hexString[5]) || !wpi::isHexDigit(hexString[6])) {
throw std::invalid_argument(
fmt::format("Invalid hex string for Color \"{}\"", hexString));
}

int r = wpi::hexDigitValue(hexString[1]) * 16 +
wpi::hexDigitValue(hexString[2]);
int g = wpi::hexDigitValue(hexString[3]) * 16 +
wpi::hexDigitValue(hexString[4]);
int b = wpi::hexDigitValue(hexString[5]) * 16 +
wpi::hexDigitValue(hexString[6]);

red = r / 255.0;
green = g / 255.0;
blue = b / 255.0;
}

constexpr bool operator==(const Color&) const = default;

/**
Expand Down
58 changes: 57 additions & 1 deletion wpilibc/src/main/native/include/frc/util/Color8Bit.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
#pragma once

#include <algorithm>
#include <stdexcept>
#include <string>
#include <string_view>

#include <fmt/core.h>
#include <wpi/StringExtras.h>

#include "Color.h"

Expand All @@ -16,6 +21,9 @@ namespace frc {
*/
class Color8Bit {
public:
/**
* Constructs a default color (black).
*/
constexpr Color8Bit() = default;

/**
Expand All @@ -40,11 +48,59 @@ class Color8Bit {
green(color.green * 255),
blue(color.blue * 255) {}

/**
* Constructs a Color8Bit from a hex string.
*
* @param hexString a string of the format <tt>\#RRGGBB</tt>
* @throws std::invalid_argument if the hex string is invalid.
*/
explicit constexpr Color8Bit(std::string_view hexString) {
if (hexString.length() != 7 || !hexString.starts_with("#") ||
!wpi::isHexDigit(hexString[1]) || !wpi::isHexDigit(hexString[2]) ||
!wpi::isHexDigit(hexString[3]) || !wpi::isHexDigit(hexString[4]) ||
!wpi::isHexDigit(hexString[5]) || !wpi::isHexDigit(hexString[6])) {
throw std::invalid_argument(
fmt::format("Invalid hex string for Color \"{}\"", hexString));
}

red = wpi::hexDigitValue(hexString[1]) * 16 +
wpi::hexDigitValue(hexString[2]);
green = wpi::hexDigitValue(hexString[3]) * 16 +
wpi::hexDigitValue(hexString[4]);
blue = wpi::hexDigitValue(hexString[5]) * 16 +
wpi::hexDigitValue(hexString[6]);
}

constexpr bool operator==(const Color8Bit&) const = default;

constexpr operator Color() const { // NOLINT
return Color(red / 255.0, green / 255.0, blue / 255.0);
}

constexpr bool operator==(const Color8Bit&) const = default;
/**
* Create a Color8Bit from a hex string.
*
* @param hexString a string of the format <tt>\#RRGGBB</tt>
* @return Color8Bit object from hex string.
* @throws std::invalid_argument if the hex string is invalid.
*/
static constexpr Color8Bit FromHexString(std::string_view hexString) {
if (hexString.length() != 7 || !hexString.starts_with("#") ||
!wpi::isHexDigit(hexString[1]) || !wpi::isHexDigit(hexString[2]) ||
!wpi::isHexDigit(hexString[3]) || !wpi::isHexDigit(hexString[4]) ||
!wpi::isHexDigit(hexString[5]) || !wpi::isHexDigit(hexString[6])) {
throw std::invalid_argument(
fmt::format("Invalid hex string for Color \"{}\"", hexString));
}

int r = wpi::hexDigitValue(hexString[0]) * 16 +
wpi::hexDigitValue(hexString[1]);
int g = wpi::hexDigitValue(hexString[2]) * 16 +
wpi::hexDigitValue(hexString[3]);
int b = wpi::hexDigitValue(hexString[4]) * 16 +
wpi::hexDigitValue(hexString[5]);
return Color8Bit{r, g, b};
}

/**
* Return this color represented as a hex string.
Expand Down
62 changes: 62 additions & 0 deletions wpilibc/src/test/native/cpp/util/Color8BitTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#include <gtest/gtest.h>

#include "frc/util/Color8Bit.h"

TEST(Color8BitTest, ConstructDefault) {
constexpr frc::Color8Bit color;

EXPECT_EQ(0, color.red);
EXPECT_EQ(0, color.green);
EXPECT_EQ(0, color.blue);
}

TEST(Color8BitTest, ConstructFromInts) {
constexpr frc::Color8Bit color{255, 128, 64};

EXPECT_EQ(255, color.red);
EXPECT_EQ(128, color.green);
EXPECT_EQ(64, color.blue);
}

TEST(Color8BitTest, ConstructFromColor) {
constexpr frc::Color8Bit color{frc::Color{255, 128, 64}};

EXPECT_EQ(255, color.red);
EXPECT_EQ(128, color.green);
EXPECT_EQ(64, color.blue);
}

TEST(Color8BitTest, ConstructFromHexString) {
constexpr frc::Color8Bit color{"#FF8040"};

EXPECT_EQ(255, color.red);
EXPECT_EQ(128, color.green);
EXPECT_EQ(64, color.blue);

// No leading #
EXPECT_THROW(frc::Color8Bit{"112233"}, std::invalid_argument);

// Too long
EXPECT_THROW(frc::Color8Bit{"#11223344"}, std::invalid_argument);

// Invalid hex characters
EXPECT_THROW(frc::Color8Bit{"#$$$$$$"}, std::invalid_argument);
}

TEST(Color8BitTest, ImplicitConversionToColor) {
frc::Color color = frc::Color8Bit{255, 128, 64};

EXPECT_NEAR(1.0, color.red, 1e-2);
EXPECT_NEAR(0.5, color.green, 1e-2);
EXPECT_NEAR(0.25, color.blue, 1e-2);
}

TEST(Color8BitTest, ToHexString) {
constexpr frc::Color8Bit color{255, 128, 64};

EXPECT_EQ("#FF8040", color.HexString());
}
62 changes: 62 additions & 0 deletions wpilibc/src/test/native/cpp/util/ColorTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.

#include <gtest/gtest.h>

#include "frc/util/Color.h"

TEST(ColorTest, ConstructDefault) {
constexpr frc::Color color;

EXPECT_DOUBLE_EQ(0.0, color.red);
EXPECT_DOUBLE_EQ(0.0, color.green);
EXPECT_DOUBLE_EQ(0.0, color.blue);
}

TEST(ColorTest, ConstructFromDoubles) {
constexpr frc::Color color{1.0, 0.5, 0.25};

EXPECT_NEAR(1.0, color.red, 1e-2);
EXPECT_NEAR(0.5, color.green, 1e-2);
EXPECT_NEAR(0.25, color.blue, 1e-2);
}

TEST(ColorTest, ConstructFromInts) {
constexpr frc::Color color{255, 128, 64};

EXPECT_NEAR(1.0, color.red, 1e-2);
EXPECT_NEAR(0.5, color.green, 1e-2);
EXPECT_NEAR(0.25, color.blue, 1e-2);
}

TEST(ColorTest, ConstructFromHexString) {
constexpr frc::Color color{"#FF8040"};

EXPECT_NEAR(1.0, color.red, 1e-2);
EXPECT_NEAR(0.5, color.green, 1e-2);
EXPECT_NEAR(0.25, color.blue, 1e-2);

// No leading #
EXPECT_THROW(frc::Color{"112233"}, std::invalid_argument);

// Too long
EXPECT_THROW(frc::Color{"#11223344"}, std::invalid_argument);

// Invalid hex characters
EXPECT_THROW(frc::Color{"#$$$$$$"}, std::invalid_argument);
}

TEST(ColorTest, FromHSV) {
constexpr frc::Color color = frc::Color::FromHSV(90, 128, 64);

EXPECT_DOUBLE_EQ(0.1256103515625, color.red);
EXPECT_DOUBLE_EQ(0.2510986328125, color.green);
EXPECT_DOUBLE_EQ(0.2510986328125, color.blue);
}

TEST(ColorTest, ToHexString) {
constexpr frc::Color color{255, 128, 64};

EXPECT_EQ("#FF8040", color.HexString());
}
23 changes: 23 additions & 0 deletions wpilibj/src/main/java/edu/wpi/first/wpilibj/util/Color.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@ public class Color {
public final double blue;
private String m_name;

/** Constructs a default color (black). */
public Color() {
red = 0.0;
green = 0.0;
blue = 0.0;
}

/**
* Constructs a Color from doubles.
*
Expand Down Expand Up @@ -69,6 +76,22 @@ private Color(double red, double green, double blue, String name) {
this.m_name = name;
}

/**
* Constructs a Color from a hex string.
*
* @param hexString a string of the format <code>#RRGGBB</code>
* @throws IllegalArgumentException if the hex string is invalid.
*/
public Color(String hexString) {
if (hexString.length() != 7 || !hexString.startsWith("#")) {
throw new IllegalArgumentException("Invalid hex string \"" + hexString + "\"");
}

this.red = Integer.valueOf(hexString.substring(1, 3), 16) / 255.0;
this.green = Integer.valueOf(hexString.substring(3, 5), 16) / 255.0;
this.blue = Integer.valueOf(hexString.substring(5, 7), 16) / 255.0;
}

/**
* Creates a Color from HSV values.
*
Expand Down
23 changes: 23 additions & 0 deletions wpilibj/src/main/java/edu/wpi/first/wpilibj/util/Color8Bit.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ public class Color8Bit {
public final int green;
public final int blue;

/** Constructs a default color (black). */
public Color8Bit() {
red = 0;
green = 0;
blue = 0;
}

/**
* Constructs a Color8Bit.
*
Expand All @@ -36,6 +43,22 @@ public Color8Bit(Color color) {
this((int) (color.red * 255), (int) (color.green * 255), (int) (color.blue * 255));
}

/**
* Constructs a Color8Bit from a hex string.
*
* @param hexString a string of the format <code>#RRGGBB</code>
* @throws IllegalArgumentException if the hex string is invalid.
*/
public Color8Bit(String hexString) {
if (hexString.length() != 7 || !hexString.startsWith("#")) {
throw new IllegalArgumentException("Invalid hex string \"" + hexString + "\"");
}

this.red = Integer.valueOf(hexString.substring(1, 3), 16);
this.green = Integer.valueOf(hexString.substring(3, 5), 16);
this.blue = Integer.valueOf(hexString.substring(5, 7), 16);
}

@Override
public boolean equals(Object other) {
if (this == other) {
Expand Down
Loading
Loading