Skip to content

Commit 2085ab3

Browse files
authored
[wpilib] Allow LED pattern gradients to be discontinuous (#7174)
1 parent 0cfff31 commit 2085ab3

File tree

5 files changed

+143
-31
lines changed

5 files changed

+143
-31
lines changed

wpilibc/src/main/native/cpp/LEDPattern.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,8 @@ LEDPattern LEDPattern::Steps(
263263
return Steps(std::span{steps.begin(), steps.end()});
264264
}
265265

266-
LEDPattern LEDPattern::Gradient(std::span<const Color> colors) {
266+
LEDPattern LEDPattern::Gradient(GradientType type,
267+
std::span<const Color> colors) {
267268
if (colors.size() == 0) {
268269
// no colors specified
269270
return LEDPattern::Off();
@@ -273,11 +274,19 @@ LEDPattern LEDPattern::Gradient(std::span<const Color> colors) {
273274
return LEDPattern::Solid(colors[0]);
274275
}
275276

276-
return LEDPattern{[colors = std::vector(colors.begin(), colors.end())](
277+
return LEDPattern{[type, colors = std::vector(colors.begin(), colors.end())](
277278
auto data, auto writer) {
278279
size_t numSegments = colors.size();
279280
auto bufLen = data.size();
280-
int ledsPerSegment = bufLen / numSegments;
281+
int ledsPerSegment = 0;
282+
switch (type) {
283+
case kContinuous:
284+
ledsPerSegment = bufLen / numSegments;
285+
break;
286+
case kDiscontinuous:
287+
ledsPerSegment = (bufLen - 1) / (numSegments - 1);
288+
break;
289+
}
281290

282291
for (size_t led = 0; led < bufLen; led++) {
283292
int colorIndex = (led / ledsPerSegment) % numSegments;
@@ -295,8 +304,9 @@ LEDPattern LEDPattern::Gradient(std::span<const Color> colors) {
295304
}};
296305
}
297306

298-
LEDPattern LEDPattern::Gradient(std::initializer_list<Color> colors) {
299-
return Gradient(std::span{colors.begin(), colors.end()});
307+
LEDPattern LEDPattern::Gradient(GradientType type,
308+
std::initializer_list<Color> colors) {
309+
return Gradient(type, std::span{colors.begin(), colors.end()});
300310
}
301311

302312
LEDPattern LEDPattern::Rainbow(int saturation, int value) {

wpilibc/src/main/native/include/frc/LEDPattern.h

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -315,29 +315,52 @@ class LEDPattern {
315315
static LEDPattern Steps(
316316
std::initializer_list<std::pair<double, Color>> steps);
317317

318+
/** Types of gradients. */
319+
enum GradientType {
320+
/**
321+
* A continuous gradient, where the gradient wraps around to allow for
322+
* seamless scrolling effects.
323+
*/
324+
kContinuous,
325+
/**
326+
* A discontinuous gradient, where the first pixel is set to the first color
327+
* of the gradient and the final pixel is set to the last color of the
328+
* gradient. There is no wrapping effect, so scrolling effects will display
329+
* an obvious seam.
330+
*/
331+
kDiscontinuous
332+
};
333+
318334
/**
319335
* Creates a pattern that displays a non-animated gradient of colors across
320-
* the entire length of the LED strip. The gradient wraps around so the start
321-
* and end of the strip are the same color, which allows the gradient to be
322-
* modified with a scrolling effect with no discontinuities. Colors are evenly
323-
* distributed along the full length of the LED strip.
324-
*
336+
* the entire length of the LED strip. Colors are evenly distributed along the
337+
* full length of the LED strip. The gradient type is configured with the
338+
* {@code type} parameter, allowing the gradient to be either continuous (no
339+
* seams, good for scrolling effects) or discontinuous (a clear seam is
340+
* visible, but the gradient applies to the full length of the LED strip
341+
* without needing to use some space for wrapping).
342+
*
343+
* @param type the type of gradient (continuous or discontinuous)
325344
* @param colors the colors to display in the gradient
326345
* @return a motionless gradient pattern
327346
*/
328-
static LEDPattern Gradient(std::span<const Color> colors);
347+
static LEDPattern Gradient(GradientType type, std::span<const Color> colors);
329348

330349
/**
331350
* Creates a pattern that displays a non-animated gradient of colors across
332-
* the entire length of the LED strip. The gradient wraps around so the start
333-
* and end of the strip are the same color, which allows the gradient to be
334-
* modified with a scrolling effect with no discontinuities. Colors are evenly
335-
* distributed along the full length of the LED strip.
336-
*
351+
* the entire length of the LED strip. Colors are evenly distributed along the
352+
* full length of the LED strip. The gradient type is configured with the
353+
* {@code type} parameter, allowing the gradient to be either continuous (no
354+
* seams, good for scrolling effects) or discontinuous (a clear seam is
355+
* visible, but the gradient applies to the full length of the LED strip
356+
* without needing to use some space for wrapping).
357+
*
358+
* @param type the type of gradient (continuous or discontinuous)
337359
* @param colors the colors to display in the gradient
338360
* @return a motionless gradient pattern
339361
*/
340-
static LEDPattern Gradient(std::initializer_list<Color> colors);
362+
static LEDPattern Gradient(GradientType type,
363+
std::initializer_list<Color> colors);
341364

342365
/**
343366
* Creates an LED pattern that displays a rainbow across the color wheel. The

wpilibc/src/test/native/cpp/LEDPatternTest.cpp

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ TEST(LEDPatternTest, SolidColor) {
4848

4949
TEST(LEDPatternTest, EmptyGradientSetsToBlack) {
5050
std::array<Color, 0> colors;
51-
LEDPattern pattern = LEDPattern::Gradient(colors);
51+
LEDPattern pattern =
52+
LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors);
5253
std::array<AddressableLED::LEDData, 5> buffer;
5354
pattern.ApplyTo(buffer);
5455
for (int i = 0; i < 5; i++) {
@@ -58,7 +59,8 @@ TEST(LEDPatternTest, EmptyGradientSetsToBlack) {
5859

5960
TEST(LEDPatternTest, SingleColorGradientSetsSolid) {
6061
std::array<Color, 1> colors{Color::kYellow};
61-
LEDPattern pattern = LEDPattern::Gradient(colors);
62+
LEDPattern pattern =
63+
LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors);
6264
std::array<AddressableLED::LEDData, 5> buffer;
6365
pattern.ApplyTo(buffer);
6466
for (int i = 0; i < 5; i++) {
@@ -68,7 +70,8 @@ TEST(LEDPatternTest, SingleColorGradientSetsSolid) {
6870

6971
TEST(LEDPatternTest, Gradient2Colors) {
7072
std::array<Color, 2> colors{Color::kYellow, Color::kPurple};
71-
LEDPattern pattern = LEDPattern::Gradient(colors);
73+
LEDPattern pattern =
74+
LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors);
7275
std::array<AddressableLED::LEDData, 99> buffer;
7376
pattern.ApplyTo(buffer);
7477
AssertIndexColor(buffer, 0, Color::kYellow);
@@ -80,9 +83,21 @@ TEST(LEDPatternTest, Gradient2Colors) {
8083
AssertIndexColor(buffer, 98, Color::kYellow);
8184
}
8285

86+
TEST(LEDPatternTest, DiscontinuousGradient2Colors) {
87+
std::array<Color, 2> colors{Color::kYellow, Color::kPurple};
88+
LEDPattern pattern =
89+
LEDPattern::Gradient(LEDPattern::GradientType::kDiscontinuous, colors);
90+
std::array<AddressableLED::LEDData, 99> buffer;
91+
pattern.ApplyTo(buffer);
92+
AssertIndexColor(buffer, 0, Color::kYellow);
93+
AssertIndexColor(buffer, 49, LerpColors(Color::kYellow, Color::kPurple, 0.5));
94+
AssertIndexColor(buffer, 98, Color::kPurple);
95+
}
96+
8397
TEST(LEDPatternTest, Gradient3Colors) {
8498
std::array<Color, 3> colors{Color::kYellow, Color::kPurple, Color::kWhite};
85-
LEDPattern pattern = LEDPattern::Gradient(colors);
99+
LEDPattern pattern =
100+
LEDPattern::Gradient(LEDPattern::GradientType::kContinuous, colors);
86101
std::array<AddressableLED::LEDData, 99> buffer;
87102
pattern.ApplyTo(buffer);
88103

@@ -99,6 +114,20 @@ TEST(LEDPatternTest, Gradient3Colors) {
99114
LerpColors(Color::kWhite, Color::kYellow, 32 / 33.0));
100115
}
101116

117+
TEST(LEDPatternTest, DiscontinuousGradient3Colors) {
118+
std::array<Color, 3> colors{Color::kYellow, Color::kPurple, Color::kWhite};
119+
LEDPattern pattern =
120+
LEDPattern::Gradient(LEDPattern::GradientType::kDiscontinuous, colors);
121+
std::array<AddressableLED::LEDData, 101> buffer;
122+
pattern.ApplyTo(buffer);
123+
124+
AssertIndexColor(buffer, 0, Color::kYellow);
125+
AssertIndexColor(buffer, 25, LerpColors(Color::kYellow, Color::kPurple, 0.5));
126+
AssertIndexColor(buffer, 50, Color::kPurple);
127+
AssertIndexColor(buffer, 75, LerpColors(Color::kPurple, Color::kWhite, 0.5));
128+
AssertIndexColor(buffer, 100, Color::kWhite);
129+
}
130+
102131
TEST(LEDPatternTest, EmptyStepsSetsToBlack) {
103132
std::array<std::pair<double, Color>, 0> steps;
104133
LEDPattern pattern = LEDPattern::Steps(steps);

wpilibj/src/main/java/edu/wpi/first/wpilibj/LEDPattern.java

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -569,16 +569,35 @@ static LEDPattern steps(Map<? extends Number, Color> steps) {
569569
};
570570
}
571571

572+
/** Types of gradients. */
573+
enum GradientType {
574+
/**
575+
* A continuous gradient, where the gradient wraps around to allow for seamless scrolling
576+
* effects.
577+
*/
578+
kContinuous,
579+
580+
/**
581+
* A discontinuous gradient, where the first pixel is set to the first color of the gradient and
582+
* the final pixel is set to the last color of the gradient. There is no wrapping effect, so
583+
* scrolling effects will display an obvious seam.
584+
*/
585+
kDiscontinuous
586+
}
587+
572588
/**
573589
* Creates a pattern that displays a non-animated gradient of colors across the entire length of
574-
* the LED strip. The gradient wraps around so the start and end of the strip are the same color,
575-
* which allows the gradient to be modified with a scrolling effect with no discontinuities.
576-
* Colors are evenly distributed along the full length of the LED strip.
590+
* the LED strip. Colors are evenly distributed along the full length of the LED strip. The
591+
* gradient type is configured with the {@code type} parameter, allowing the gradient to be either
592+
* continuous (no seams, good for scrolling effects) or discontinuous (a clear seam is visible,
593+
* but the gradient applies to the full length of the LED strip without needing to use some space
594+
* for wrapping).
577595
*
596+
* @param type the type of gradient (continuous or discontinuous)
578597
* @param colors the colors to display in the gradient
579598
* @return a motionless gradient pattern
580599
*/
581-
static LEDPattern gradient(Color... colors) {
600+
static LEDPattern gradient(GradientType type, Color... colors) {
582601
if (colors.length == 0) {
583602
// Nothing to display
584603
DriverStation.reportWarning("Creating a gradient with no colors!", false);
@@ -595,7 +614,11 @@ static LEDPattern gradient(Color... colors) {
595614

596615
return (reader, writer) -> {
597616
int bufLen = reader.getLength();
598-
int ledsPerSegment = bufLen / numSegments;
617+
int ledsPerSegment =
618+
switch (type) {
619+
case kContinuous -> bufLen / numSegments;
620+
case kDiscontinuous -> (bufLen - 1) / (numSegments - 1);
621+
};
599622

600623
for (int led = 0; led < bufLen; led++) {
601624
int colorIndex = (led / ledsPerSegment) % numSegments;

wpilibj/src/test/java/edu/wpi/first/wpilibj/LEDPatternTest.java

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
import static edu.wpi.first.units.Units.Percent;
1212
import static edu.wpi.first.units.Units.Seconds;
1313
import static edu.wpi.first.units.Units.Value;
14+
import static edu.wpi.first.wpilibj.LEDPattern.GradientType.kContinuous;
15+
import static edu.wpi.first.wpilibj.LEDPattern.GradientType.kDiscontinuous;
1416
import static edu.wpi.first.wpilibj.util.Color.kBlack;
1517
import static edu.wpi.first.wpilibj.util.Color.kBlue;
1618
import static edu.wpi.first.wpilibj.util.Color.kLime;
@@ -80,7 +82,7 @@ void solidColor() {
8082

8183
@Test
8284
void gradient0SetsToBlack() {
83-
LEDPattern pattern = LEDPattern.gradient();
85+
LEDPattern pattern = LEDPattern.gradient(kContinuous);
8486
AddressableLEDBuffer buffer = new AddressableLEDBuffer(99);
8587
for (int i = 0; i < buffer.getLength(); i++) {
8688
buffer.setRGB(i, 127, 128, 129);
@@ -95,7 +97,7 @@ void gradient0SetsToBlack() {
9597

9698
@Test
9799
void gradient1SetsToSolid() {
98-
LEDPattern pattern = LEDPattern.gradient(kYellow);
100+
LEDPattern pattern = LEDPattern.gradient(kContinuous, kYellow);
99101

100102
AddressableLEDBuffer buffer = new AddressableLEDBuffer(99);
101103
pattern.applyTo(buffer);
@@ -106,8 +108,8 @@ void gradient1SetsToSolid() {
106108
}
107109

108110
@Test
109-
void gradient2Colors() {
110-
LEDPattern pattern = LEDPattern.gradient(kYellow, kPurple);
111+
void continuousGradient2Colors() {
112+
LEDPattern pattern = LEDPattern.gradient(kContinuous, kYellow, kPurple);
111113

112114
AddressableLEDBuffer buffer = new AddressableLEDBuffer(99);
113115
pattern.applyTo(buffer);
@@ -119,9 +121,21 @@ void gradient2Colors() {
119121
assertColorEquals(kYellow, buffer.getLED(98));
120122
}
121123

124+
@Test
125+
void discontinuousGradient2Colors() {
126+
LEDPattern pattern = LEDPattern.gradient(kDiscontinuous, kYellow, kPurple);
127+
128+
AddressableLEDBuffer buffer = new AddressableLEDBuffer(99);
129+
pattern.applyTo(buffer);
130+
131+
assertColorEquals(kYellow, buffer.getLED(0));
132+
assertColorEquals(Color.lerpRGB(kYellow, kPurple, 0.5), buffer.getLED(49));
133+
assertColorEquals(kPurple, buffer.getLED(98));
134+
}
135+
122136
@Test
123137
void gradient3Colors() {
124-
LEDPattern pattern = LEDPattern.gradient(kYellow, kPurple, kWhite);
138+
LEDPattern pattern = LEDPattern.gradient(kContinuous, kYellow, kPurple, kWhite);
125139
AddressableLEDBuffer buffer = new AddressableLEDBuffer(99);
126140
pattern.applyTo(buffer);
127141

@@ -134,6 +148,19 @@ void gradient3Colors() {
134148
assertColorEquals(Color.lerpRGB(kWhite, kYellow, 32.0 / 33.0), buffer.getLED(98));
135149
}
136150

151+
@Test
152+
void discontinuousGradient3Colors() {
153+
LEDPattern pattern = LEDPattern.gradient(kDiscontinuous, kYellow, kPurple, kWhite);
154+
AddressableLEDBuffer buffer = new AddressableLEDBuffer(101);
155+
pattern.applyTo(buffer);
156+
157+
assertColorEquals(kYellow, buffer.getLED(0));
158+
assertColorEquals(Color.lerpRGB(kYellow, kPurple, 0.5), buffer.getLED(25));
159+
assertColorEquals(kPurple, buffer.getLED(50));
160+
assertColorEquals(Color.lerpRGB(kPurple, kWhite, 0.5), buffer.getLED(75));
161+
assertColorEquals(kWhite, buffer.getLED(100));
162+
}
163+
137164
@Test
138165
void step0SetsToBlack() {
139166
LEDPattern pattern = LEDPattern.steps(Map.of());

0 commit comments

Comments
 (0)