001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package edu.wpi.first.wpilibj; 006 007import static edu.wpi.first.util.ErrorMessages.requireNonNullParam; 008 009import edu.wpi.first.wpilibj.util.Color; 010import edu.wpi.first.wpilibj.util.Color8Bit; 011 012/** 013 * A view of another addressable LED buffer. Views CANNOT be written directly to an LED strip; the 014 * backing buffer must be written instead. However, views provide an easy way to split a large LED 015 * strip into smaller sections (which may be reversed from the orientation of the LED strip as a 016 * whole) that can be animated individually without modifying LEDs outside those sections. 017 */ 018public class AddressableLEDBufferView implements LEDReader, LEDWriter { 019 private final LEDReader m_backingReader; 020 private final LEDWriter m_backingWriter; 021 private final int m_startingIndex; 022 private final int m_endingIndex; 023 private final int m_length; 024 025 /** 026 * Creates a new view of a buffer. A view will be reversed if the starting index is after the 027 * ending index; writing front-to-back in the view will write in the back-to-front direction on 028 * the underlying buffer. 029 * 030 * @param backingBuffer the backing buffer to view 031 * @param startingIndex the index of the LED in the backing buffer that the view should start from 032 * @param endingIndex the index of the LED in the backing buffer that the view should end on 033 * @param <B> the type of the buffer object to create a view for 034 */ 035 public <B extends LEDReader & LEDWriter> AddressableLEDBufferView( 036 B backingBuffer, int startingIndex, int endingIndex) { 037 this( 038 requireNonNullParam(backingBuffer, "backingBuffer", "AddressableLEDBufferView"), 039 backingBuffer, 040 startingIndex, 041 endingIndex); 042 } 043 044 /** 045 * Creates a new view of a buffer. A view will be reversed if the starting index is after the 046 * ending index; writing front-to-back in the view will write in the back-to-front direction on 047 * the underlying buffer. 048 * 049 * @param backingReader the backing LED data reader 050 * @param backingWriter the backing LED data writer 051 * @param startingIndex the index of the LED in the backing buffer that the view should start from 052 * @param endingIndex the index of the LED in the backing buffer that the view should end on 053 */ 054 public AddressableLEDBufferView( 055 LEDReader backingReader, LEDWriter backingWriter, int startingIndex, int endingIndex) { 056 requireNonNullParam(backingReader, "backingReader", "AddressableLEDBufferView"); 057 requireNonNullParam(backingWriter, "backingWriter", "AddressableLEDBufferView"); 058 059 if (startingIndex < 0 || startingIndex >= backingReader.getLength()) { 060 throw new IndexOutOfBoundsException("Start index out of range: " + startingIndex); 061 } 062 if (endingIndex < 0 || endingIndex >= backingReader.getLength()) { 063 throw new IndexOutOfBoundsException("End index out of range: " + endingIndex); 064 } 065 066 m_backingReader = backingReader; 067 m_backingWriter = backingWriter; 068 069 m_startingIndex = startingIndex; 070 m_endingIndex = endingIndex; 071 m_length = Math.abs(endingIndex - startingIndex) + 1; 072 } 073 074 /** 075 * Creates a view that operates on the same range as this one, but goes in reverse order. This is 076 * useful for serpentine runs of LED strips connected front-to-end; simply reverse the view for 077 * reversed sections and animations will move in the same physical direction along both strips. 078 * 079 * @return the reversed view 080 */ 081 public AddressableLEDBufferView reversed() { 082 return new AddressableLEDBufferView(this, m_length - 1, 0); 083 } 084 085 @Override 086 public int getLength() { 087 return m_length; 088 } 089 090 @Override 091 public void setRGB(int index, int r, int g, int b) { 092 m_backingWriter.setRGB(nativeIndex(index), r, g, b); 093 } 094 095 @Override 096 public Color getLED(int index) { 097 // override to delegate to the backing buffer to avoid 3x native index lookups & bounds checks 098 return m_backingReader.getLED(nativeIndex(index)); 099 } 100 101 @Override 102 public Color8Bit getLED8Bit(int index) { 103 // override to delegate to the backing buffer to avoid 3x native index lookups & bounds checks 104 return m_backingReader.getLED8Bit(nativeIndex(index)); 105 } 106 107 @Override 108 public int getRed(int index) { 109 return m_backingReader.getRed(nativeIndex(index)); 110 } 111 112 @Override 113 public int getGreen(int index) { 114 return m_backingReader.getGreen(nativeIndex(index)); 115 } 116 117 @Override 118 public int getBlue(int index) { 119 return m_backingReader.getBlue(nativeIndex(index)); 120 } 121 122 /** 123 * Checks if this view is reversed with respect to its backing buffer. 124 * 125 * @return true if the view is reversed, false otherwise 126 */ 127 public boolean isReversed() { 128 return m_endingIndex < m_startingIndex; 129 } 130 131 /** 132 * Converts a view-local index in the range [start, end] to a global index in the range [0, 133 * length]. 134 * 135 * @param viewIndex the view-local index 136 * @return the corresponding global index 137 * @throws IndexOutOfBoundsException if the view index is not contained within the bounds of this 138 * view 139 */ 140 private int nativeIndex(int viewIndex) { 141 if (isReversed()) { 142 // 0 1 2 3 4 5 6 7 8 9 10 143 // ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 144 // [_, _, _, _, (d, c, b, a), _, _, _] 145 // ↑ ↑ ↑ ↑ 146 // 3 2 1 0 147 if (viewIndex < 0 || viewIndex > m_startingIndex) { 148 throw new IndexOutOfBoundsException(viewIndex); 149 } 150 return m_startingIndex - viewIndex; 151 } else { 152 // 0 1 2 3 4 5 6 7 8 9 10 153 // ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 154 // [_, _, _, _, (a, b, c, d), _, _, _] 155 // ↑ ↑ ↑ ↑ 156 // 0 1 2 3 157 if (viewIndex < 0 || viewIndex > m_endingIndex) { 158 throw new IndexOutOfBoundsException(viewIndex); 159 } 160 return m_startingIndex + viewIndex; 161 } 162 } 163}