1+ package org .steelhawks .subsystems .led .anim ;
2+
3+ import org .steelhawks .subsystems .led .LEDMatrix ;
4+
5+ /**
6+ * LIGHTWEIGHT Frame-Based Animation for LED Matrix
7+ * Pre-bake your animations as frame arrays - no GIF decoding at runtime!
8+ */
9+ public class FrameAnimation extends LEDMatrix .Animation {
10+
11+ private final int [][][] frames ; // [frame][x][y] = RGB packed int
12+ private final int [] frameDelays ; // delay in periodic cycles
13+ private int currentFrameIndex = 0 ;
14+ private int frameDelayCounter = 0 ;
15+ private final boolean loop ;
16+ private boolean finished = false ;
17+
18+ /**
19+ * Create animation from pre-computed frame data
20+ * @param frames Array of frames, each frame is [x][y] RGB packed as int (0xRRGGBB)
21+ * @param frameDelays Delay for each frame in 20ms cycles (1 = 20ms, 2 = 40ms, etc.)
22+ * @param loop Whether to loop
23+ */
24+ public FrameAnimation (int [][][] frames , int [] frameDelays , boolean loop ) {
25+ this .frames = frames ;
26+ this .frameDelays = frameDelays ;
27+ this .loop = loop ;
28+ }
29+
30+ /**
31+ * Create animation with uniform frame delay
32+ */
33+ public FrameAnimation (int [][][] frames , int frameDelay , boolean loop ) {
34+ this .frames = frames ;
35+ this .frameDelays = new int [frames .length ];
36+ for (int i = 0 ; i < frames .length ; i ++) {
37+ this .frameDelays [i ] = frameDelay ;
38+ }
39+ this .loop = loop ;
40+ }
41+
42+ @ Override
43+ public void render (LEDMatrix matrix ) {
44+ if (finished || frames .length == 0 ) return ;
45+
46+ int [][] currentFrame = frames [currentFrameIndex ];
47+
48+ // Draw current frame - optimized
49+ for (int x = 0 ; x < Math .min (currentFrame .length , matrix .getWidth ()); x ++) {
50+ for (int y = 0 ; y < Math .min (currentFrame [x ].length , matrix .getHeight ()); y ++) {
51+ int rgb = currentFrame [x ][y ];
52+ int r = (rgb >> 16 ) & 0xFF ;
53+ int g = (rgb >> 8 ) & 0xFF ;
54+ int b = rgb & 0xFF ;
55+ matrix .setPixel (x , y , new LEDMatrix .Color (r , g , b ));
56+ }
57+ }
58+
59+ // Handle frame timing
60+ frameDelayCounter ++;
61+ if (frameDelayCounter >= frameDelays [currentFrameIndex ]) {
62+ frameDelayCounter = 0 ;
63+ currentFrameIndex ++;
64+
65+ if (currentFrameIndex >= frames .length ) {
66+ if (loop ) {
67+ currentFrameIndex = 0 ;
68+ } else {
69+ finished = true ;
70+ currentFrameIndex = frames .length - 1 ;
71+ }
72+ }
73+ }
74+ }
75+
76+ @ Override
77+ public void reset () {
78+ super .reset ();
79+ currentFrameIndex = 0 ;
80+ frameDelayCounter = 0 ;
81+ finished = false ;
82+ }
83+ }
84+
85+ // ==================== USAGE GUIDE ====================
86+
87+ /*
88+ HOW TO CREATE CUSTOM ANIMATIONS:
89+
90+ Method 1: Hand-code simple animations (shown above)
91+ -------------------------------------------------
92+ public static FrameAnimation myAnimation() {
93+ int[][][] frames = new int[numFrames][width][height];
94+
95+ // Fill each frame with RGB values
96+ frames[0][x][y] = 0xFF0000; // Red pixel at (x,y) in frame 0
97+ frames[1][x][y] = 0x00FF00; // Green pixel
98+
99+ return new FrameAnimation(frames, frameDelay, loop);
100+ }
101+
102+
103+ Method 2: Use Python script to convert GIF → Java array
104+ --------------------------------------------------------
105+ Create a Python script to pre-process GIFs into frame arrays:
106+
107+ ```python
108+ from PIL import Image
109+ import os
110+
111+ def gif_to_java_frames(gif_path, output_name):
112+ img = Image.open(gif_path)
113+ frames = []
114+
115+ try:
116+ while True:
117+ # Resize to target size
118+ resized = img.resize((32, 8), Image.NEAREST)
119+ rgb_img = resized.convert('RGB')
120+
121+ # Extract pixels
122+ frame_data = []
123+ for x in range(32):
124+ col = []
125+ for y in range(8):
126+ r, g, b = rgb_img.getpixel((x, y))
127+ rgb_int = (r << 16) | (g << 8) | b
128+ col.append(f"0x{rgb_int:06X}")
129+ frame_data.append(col)
130+ frames.append(frame_data)
131+
132+ img.seek(img.tell() + 1)
133+ except EOFError:
134+ pass
135+
136+ # Generate Java code
137+ java_code = f"public static FrameAnimation {output_name}() {{\n"
138+ java_code += f" int[][][] frames = new int[{len(frames)}][32][8];\n\n"
139+
140+ for i, frame in enumerate(frames):
141+ java_code += f" // Frame {i}\n"
142+ for x, col in enumerate(frame):
143+ java_code += f" frames[{i}][{x}] = new int[]{{ {', '.join(col)} }};\n"
144+ java_code += "\n"
145+
146+ java_code += " return new FrameAnimation(frames, 2, true);\n"
147+ java_code += "}\n"
148+
149+ return java_code
150+
151+ # Usage:
152+ print(gif_to_java_frames("fire.gif", "fireAnimation"))
153+ ```
154+
155+ Then copy-paste the output into AnimationLibrary.java
156+
157+
158+ Method 3: Load from CSV at robot init (one-time load)
159+ ------------------------------------------------------
160+ Create CSV files with frame data, load once at startup:
161+
162+ frames.csv format:
163+ frame,x,y,r,g,b
164+ 0,0,0,255,0,0
165+ 0,0,1,255,0,0
166+ ...
167+
168+ Then load in robot init (NOT during match):
169+ ```java
170+ public static FrameAnimation loadFromCSV(String path) {
171+ // Parse CSV and build frame array
172+ // This only runs ONCE at robot startup
173+ }
174+ ```
175+
176+
177+ USAGE IN CODE:
178+ --------------
179+ private void configureBindings() {
180+ // Pre-made animations (zero overhead)
181+ driver.a().onTrue(leds.frameAnimationCommand(
182+ AnimationLibrary.simpleBlinkRed8x8()
183+ ));
184+
185+ driver.b().onTrue(leds.frameAnimationCommand(
186+ AnimationLibrary.scanningLine32x8()
187+ ));
188+ }
189+
190+
191+ PERFORMANCE:
192+ -----------
193+ ✅ Pre-computed frames: Fast
194+ ✅ Simple array lookups: Minimal CPU
195+ ✅ No file I/O during match: Safe
196+ ✅ Memory usage: Reasonable (a 32x8x30-frame animation ≈ 7.6KB)
197+
198+ MEMORY CALCULATION:
199+ ------------------
200+ Memory per animation = width × height × frames × 4 bytes
201+ 32×8×30 = 7,680 pixels × 4 bytes = ~31KB
202+
203+ Keep total animations under 500KB to be safe on roboRIO.
204+ */
0 commit comments