forked from DrBrainlove/Mimsy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGeneratorPalette.java
430 lines (358 loc) · 12.1 KB
/
GeneratorPalette.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
import heronarts.lx.color.*;
import java.awt.Color;
public class GeneratorPalette {
/////////////////////////////////////////////////////////////// ENUMERATIONS
// Color Scheme (for color scheme generation)
public enum ColorScheme {
Analogous,
Analogous30,
Analogous45,
Analogous60,
Analogous90,
Monochromatic,
Triad,
Complementary,
Custom
}
// What to do when we get to the end of the color palette
public enum LoopPattern { Default, Repeat, Reverse, Cycle }
// Color Space flags that modulate certain algorithms
public enum ColorSpace { RGB, HSB, CIE }
/////////////////////////////////////////////////// COLOR CYCLE PHASE STRUCT
/**
* The full color cycle is broken into phases of interpolation between
* reference colors.
*/
private class ColorCyclePhase {
int index = 0; // which phase
int color_start = 0; // starting color index
int color_end = 0; // target color index
double progress_start = 0; // starting point for total progress
double progress_end = 0; // stopping point for total progress
ColorCyclePhase(int i, int cs, int ce, double ps, double pe) {
index = i;
color_start = cs;
color_end = ce;
progress_start = ps;
progress_end = pe;
//System.out.format("I: %d %.2f-%.2f %d-%d\n", i, ps, pe, cs, ce);
}
}
////////////////////////////////////////////////////////// GENERATOR PALETTE
//======================================================= INSTANCE VARIABLES
// reference color color palette
public ColorOffset color;
public ColorOffset[] palette;
// generator parameters
public ColorSpace space = ColorSpace.HSB;
public ColorScheme scheme = null;
public LoopPattern loop = null;
public double AnalogSpin = 0;
public double MonochromeLight = 40.;
public double MonochromeDark = -40.;
// steps in cycle and progress record
public int steps;
private double step_size = 0.0;
private double progress = 0.0;
// phases of the color cycle
private int phase_count = 0;
private ColorCyclePhase[] phases = null;
private boolean internalValueUpdate = false;
//============================================================= CONSTRUCTORS
/**
* A color palette that algorithmically generates a new color at each
* step based on the selected color scheme.
*
* @param scheme Analogous, Monochromatic, Triad, Complementary
* @param base_color Color from which to generate the palette
* @param loop How to loop once reaching the end of the palette
* @param steps Number of pixels or timesteps in a comlpete cycle
*/
public GeneratorPalette( int color, ColorScheme scheme, int steps ) {
this(new ColorOffset(String.format("GP-Base #%x", color), color), scheme, steps);
}
public GeneratorPalette( ColorOffset color, ColorScheme scheme, int steps ) {
this.color = color;
this.scheme = scheme;
this.steps = steps;
this.loop = getDefaultLoop();
this.space = getDefaultSpace();
reset();
updateSteps();
updatePalette();
updatePhases();
}
/*
// Want to generate scheme based on a color
GP(color, scheme, steps);
GP(ColorParameter, schema, steps);
GP(ColorOffset, schema, steps);
// Have palettes already, so don't have to generate secondary colors
GP(color[], steps);
GP(ColorParameter[], steps);
GP(ColorOffset[], steps);
// Complexest
*/
//================================================================== SETTERS
/**
* Set a new base color and regenerate the palette.
*/
public GeneratorPalette setColor(int color) {
this.color = new ColorOffset(color);
updatePalette();
return this;
}
public GeneratorPalette setColor(ColorOffset color) {
this.color = color;
updatePalette();
return this;
}
/**
* Set a new color scheme and regenerate the palette.
*/
public GeneratorPalette setScheme(ColorScheme scheme) {
// TODO: wonder if this is a good idea
this.scheme = scheme;
updatePalette();
return this;
}
/**
* Set a new color space (RGB/HSV) for color interpolation.
*/
public GeneratorPalette setSpace(ColorSpace space) {
this.space = space;
return this;
}
/**
* Set steps in color cycle.
*/
public GeneratorPalette setSteps(int steps) {
this.steps=steps;
updateSteps();
return this;
}
private void updateSteps() {
this.step_size = 1.0/(double)steps;
}
//=========================================================== INITIALIZATION
/**
* Get the default interpolation space for each color scheme.
*/
private ColorSpace getDefaultSpace() {
switch(scheme) {
case Analogous: return ColorSpace.HSB;
case Monochromatic: return ColorSpace.HSB;
case Triad: return ColorSpace.HSB;
case Complementary: return ColorSpace.RGB;
case Custom: return ColorSpace.HSB;
default: return ColorSpace.HSB;
}
}
/**
* Get the default looping pattern for each color scheme.
*/
private LoopPattern getDefaultLoop() {
switch(scheme) {
case Analogous: return LoopPattern.Reverse;
case Monochromatic: return LoopPattern.Reverse;
case Triad: return LoopPattern.Cycle;
case Complementary: return LoopPattern.Reverse;
case Custom: return LoopPattern.Reverse;
default: return LoopPattern.Repeat;
}
}
/**
* Initialize color palettes from the base color.
*/
private void updatePalette() {
switch (scheme) {
case Analogous: AnalogSpin = 30.; scheme=ColorScheme.Analogous; break;
case Analogous30: AnalogSpin = 30.; scheme=ColorScheme.Analogous; break;
case Analogous45: AnalogSpin = 45.; scheme=ColorScheme.Analogous; break;
case Analogous60: AnalogSpin = 60.; scheme=ColorScheme.Analogous; break;
case Analogous90: AnalogSpin = 90.; scheme=ColorScheme.Analogous; break;
//case Triad: AnalogSpin = 120; scheme=Analogous; break;
}
switch (scheme) {
case Analogous:
palette = new ColorOffset[] {color.clone().spin(-AnalogSpin),
color,
color.clone().spin( AnalogSpin)};
break;
case Monochromatic:
palette = new ColorOffset[] {color.clone().setBrightness(MonochromeLight),
color,
color.clone().setBrightness(MonochromeDark)};
break;
case Triad:
palette = new ColorOffset[] {color.clone().spin(-120.),
color,
color.clone().spin( 120.)};
break;
case Complementary:
palette = new ColorOffset[] { color,
color.clone().spin( 180.)};
break;
case Custom:
break;
default:
throw new RuntimeException(
"Don't know how to interpret color scheme " + scheme);
}
updatePhases();
}
/**
* Initialize color cycle looping into phases.
*/
private void updatePhases() {
if (loop == LoopPattern.Repeat) {
phase_count = palette.length-1;
phases = new ColorCyclePhase[phase_count];
int p = 0;
for (int i=0; i<palette.length-1; i++) {
phases[p] = new ColorCyclePhase(p, i, i+1,
(double)(p )/(double)phase_count,
(double)(p+1)/(double)phase_count);
p++;
}
} else if (loop == LoopPattern.Reverse) {
phase_count = (palette.length-1) * 2;
phases = new ColorCyclePhase[phase_count];
int p = 0;
for (int i=0; i<palette.length-1; i++) {
phases[p] = new ColorCyclePhase(p, i, i+1,
(double)(p )/(double)phase_count,
(double)(p+1)/(double)phase_count);
p++;
}
for (int i=0; i<palette.length-1; i++) {
phases[p] = new ColorCyclePhase(p, palette.length-i-1, palette.length-i-2,
(double)(p )/(double)phase_count,
(double)(p+1)/(double)phase_count);
p++;
}
} else if (loop == LoopPattern.Cycle) {
phase_count = palette.length;
phases = new ColorCyclePhase[phase_count];
int p = 0;
for (int i=0; i<palette.length; i++) {
phases[p] = new ColorCyclePhase(p, i, (i+1)%phase_count,
(double)(p )/(double)phase_count,
(double)(p+1)/(double)phase_count);
p++;
}
}
}
//=============================================================== GET COLORS
/**
* Generate the next color in the sequence.
*/
public int getColor() {
this.progress %= 1.0;
int color = getColor(this.progress);
this.progress += this.step_size;
return color;
}
/**
* Generate color from a given step in the sequence.
*/
public int getColor(int step) {
return getColor((double)step/(double)steps);
}
/**
* Generate color from a given progress point.
*/
public int getColor(double progress) {
progress %= 1.f;
int phase = (int)Math.floor(progress*this.phase_count);
ColorCyclePhase p = this.phases[phase];
double pp = (this.progress-p.progress_start)*(double)this.phase_count;
int color;
if (space == ColorSpace.RGB) {
color = LXColor.lerp(this.palette[p.color_start].getColor(),
this.palette[p.color_end].getColor(),
pp);
} else {
double[] c = lerpHSB(palette[p.color_start].getHSB(),
palette[p.color_end].getHSB(),
pp);
color = HSBtoColor(c);
}
return color;
}
//=========================================================== RESET PROGRESS
/**
* Restart the palette at the beginning of the cycle. Allows the same
* generator to be used for multiple objects with consistent results.
*/
public void reset() {
reset(0.f);
}
/**
* Reset the palette to a given step [0-steps].
*/
public void reset(int step) {
reset((double)step/(double)this.steps);
}
/**
* Reset the palette to a given progress [0.0-1.0].
*/
public void reset(double progress) {
this.progress %= 1.f;
this.progress = progress;
}
/**
* Linear interpolation for color triples, either RGB or HSV.
*/
public int[] lerpRGB(int[] c1, int[] c2, double amount) {
return ColorToRGB(LXColor.lerp(RGBtoColor(c1), RGBtoColor(c2), amount));
/*
int[] c3 = new int[3];
for (int i=0; i<3; i++) {
c3[i] = (int)Math.floor(((1.0-amount)*(double)c1[i]
+ amount*(double)c2[i]));
}
return c3;
*/
}
public double[] lerpHSB(double[] c1, double[] c2, double amount) {
double[] c3 = new double[3];
for (int i=0; i<3; i++) {
c3[i] = (1.0-amount)*c1[i] + amount*c2[i];
}
return c3;
}
//============================================= CONVENIENCE COLOR OPERATIONS
/**
* Standardized convenience HSB/RGB converters
*/
public double[] ColorToHSB(int color) {
return new double[] {LXColor.h(color), LXColor.s(color), LXColor.b(color)};
}
public int[] ColorToRGB(int color) {
return new int[] {LXColor.red(color),
LXColor.green(color),
LXColor.blue(color)};
}
public int RGBtoColor(int[] rgb) {
System.out.format("R: %d G: %d B: %d\n", rgb[0], rgb[1], rgb[2]);
return new Color((float)rgb[0]/256.f,
(float)rgb[1]/256.f,
(float)rgb[2]/256.f).getRGB();
}
public int HSBtoColor(double[] hsb) {
return LXColor.hsb(hsb[0], hsb[1], hsb[2]);
}
public int HSBtoColor(float[] hsb) {
return LXColor.hsb(hsb[0], hsb[1], hsb[2]);
}
public int complementary(int color) {
return addDegrees(color, 180.0f);
}
public int addDegrees(int color, float degrees) {
double[] hsb = ColorToHSB(color);
hsb[0] += degrees;
hsb[0] %= 360.f;
return LXColor.hsb(hsb[0], hsb[1], hsb[2]);
}
}