Simplify Color & Rectangle handling, fix bug.

We can just keep Color in its ARGB format and have a single static brightness()
helper. Reactangle grew a union method which allows us to avoid AWT's Rectangle
entirely.

stringsToList() needed to return null for null array input, and I went ahead
and used Arrays.asList while I was in there because we know the provenance of
the input array, no one will mutate it.
This commit is contained in:
Michael Bayne
2018-09-05 16:06:27 -07:00
parent f8efdea9ed
commit 108f87d4c7
9 changed files with 63 additions and 206 deletions
@@ -86,7 +86,7 @@ public class Application
public final String name;
/** A background color, just in case. */
public final Color background;
public final int background;
/** Background image specifiers for `RotatingBackgrounds`. */
public final List<String> rotatingBackgrounds;
@@ -107,19 +107,19 @@ public class Application
public final Rectangle progress;
/** The color of the progress text. */
public final Color progressText;
public final int progressText;
/** The color of the progress bar. */
public final Color progressBar;
public final int progressBar;
/** The dimensions of the status display. */
public final Rectangle status;
/** The color of the status text. */
public final Color statusText;
public final int statusText;
/** The color of the text shadow. */
public final Color textShadow;
public final int textShadow;
/** Where to point the user for help with install errors. */
public final String installError;
@@ -164,21 +164,19 @@ public class Application
this.progressText = config.getColor("ui.progress_text", Color.BLACK);
this.hideProgressText = config.getBoolean("ui.hide_progress_text");
this.minShowSeconds = config.getInt("ui.min_show_seconds", 5);
this.progressBar = config.getColor("ui.progress_bar", new Color(0x66, 0x99, 0xCC));
this.progressBar = config.getColor("ui.progress_bar", 0x6699CC);
this.status = config.getRect("ui.status", new Rectangle(5, 25, 500, 100));
this.statusText = config.getColor("ui.status_text", Color.BLACK);
this.textShadow = config.getColor("ui.text_shadow", null);
this.textShadow = config.getColor("ui.text_shadow", Color.CLEAR);
this.hideDecorations = config.getBoolean("ui.hide_decorations");
this.backgroundImage = config.getString("ui.background_image",
config.getString("ui.background")); // support legacy format
// and now ui.background can refer to the background color, but fall back to black
// or white, depending on the brightness of the progressText
Color defaultBackground = (.5f < this.progressText.brightness())
? Color.BLACK
: Color.WHITE;
this.backgroundImage = config.getString("ui.background_image");
// default to black or white bg color, depending on the brightness of the progressText
int defaultBackground = (0.5f < Color.brightness(this.progressText)) ?
Color.BLACK : Color.WHITE;
this.background = config.getColor("ui.background", defaultBackground);
this.progressImage = config.getString("ui.progress_image");
this.rotatingBackgrounds = stringsToList(config.getMultiValue("ui.rotating_background"));
this.rotatingBackgrounds = stringsToList(
config.getMultiValue("ui.rotating_background"));
this.iconImages = stringsToList(config.getMultiValue("ui.icon"));
this.errorBackground = config.getString("ui.error_background");
@@ -192,7 +190,7 @@ public class Application
this.patchNotesUrl = config.getUrl("ui.patch_notes_url", null);
// step progress percentage (defaults and then customized values)
EnumMap<Step, List<Integer>> stepPercentages = new EnumMap<Step, List<Integer>>(Step.class);
EnumMap<Step, List<Integer>> stepPercentages = new EnumMap<>(Step.class);
for (Step step : Step.values()) {
stepPercentages.put(step, step.defaultPercents);
}
@@ -1640,11 +1638,7 @@ public class Application
*/
public static List<String> stringsToList (String[] values)
{
List<String> list = new ArrayList<>(values.length);
for (String val : values) {
list.add(val);
}
return Collections.unmodifiableList(list);
return values == null ? null : Collections.unmodifiableList(Arrays.asList(values));
}
/** Used to parse resources with the specified name. */
@@ -6,76 +6,22 @@
package com.threerings.getdown.util;
/**
* An immutable color.
* Utilities for handling ARGB colors.
*/
public class Color
{
public final static Color WHITE = new Color(255, 255, 255);
public final static Color BLACK = new Color(0, 0, 0);
public final static int CLEAR = 0x00000000;
public final static int WHITE = 0xFFFFFFFF;
public final static int BLACK = 0xFF000000;
public final int red;
public final int green;
public final int blue;
public final int alpha;
public Color (int r, int g, int b)
{
this.red = Math.min(Math.max(r, 0), 255);
this.green = Math.min(Math.max(g, 0), 255);
this.blue = Math.min(Math.max(b, 0), 255);
this.alpha = 255;
}
public Color (int rgba, boolean hasAlpha)
{
this.red = (rgba >> 16) & 0xFF;
this.green = (rgba >> 8) & 0xFF;
this.blue = (rgba >> 0) & 0xFF;
if (hasAlpha) {
this.alpha = (rgba >> 24) & 0xFF;
} else {
this.alpha = 255;
}
}
/**
* Returns the combined RBGA value for this color.
*/
public int rgba ()
{
return ((alpha & 0xFF) << 24) |
((red & 0xFF) << 16) |
((green & 0xFF) << 8) |
((blue & 0xFF) << 0);
}
/**
* Returns the brightness of this color, per the standard HSB model.
*/
public float brightness ()
{
public static float brightness (int argb) {
// TODO: we're ignoring alpha here...
int red = (argb >> 16) & 0xFF;
int green = (argb >> 8) & 0xFF;
int blue = (argb >> 0) & 0xFF;
int max = Math.max(Math.max(red, green), blue);
return ((float) max) / 255.0f;
}
/** {@inheritDoc} */
@Override
public boolean equals(Object other)
{
return other instanceof Color && ((Color)other).rgba() == this.rgba();
}
/** {@inheritDoc} */
@Override
public int hashCode()
{
return rgba();
}
/** {@inheritDoc} */
public String toString ()
{
return getClass().getName() + "[r=" + red + ", g=" + green + ", b=" + blue + ", a=" + alpha + "]";
}
private Color () {}
}
@@ -148,16 +148,16 @@ public class Config
}
/**
* Parses the given hex color value (e.g. FFFFF) and returns a Color object with that value.
* If the given value is null of not a valid hexadecimal number, this will return null.
* Parses the given hex color value (e.g. FFCC99) and returns an {@code Integer} with that
* value. If the given value is null or not a valid hexadecimal number, this will return null.
*/
public static Color parseColor (String hexValue)
public static Integer parseColor (String hexValue)
{
if (!StringUtil.isBlank(hexValue)) {
try {
int rgba = Integer.parseInt(hexValue, 16);
boolean hasAlpha = hexValue.length() > 6;
return new Color(rgba, hasAlpha);
// if no alpha channel is specified, use 255 (full alpha)
int alpha = hexValue.length() > 6 ? 0 : 0xFF000000;
return Integer.parseInt(hexValue, 16) | alpha;
} catch (NumberFormatException e) {
log.warning("Ignoring invalid color", "hexValue", hexValue, "exception", e);
}
@@ -300,10 +300,10 @@ public class Config
}
/** Used to parse color specifications from the config file. */
public Color getColor (String name, Color def)
public int getColor (String name, int def)
{
String value = getString(name);
Color color = parseColor(value);
Integer color = parseColor(value);
return (color == null) ? def : color;
}
@@ -23,9 +23,18 @@ public class Rectangle
this.height = height;
}
public Rectangle union (Rectangle other) {
int x1 = Math.min(x, other.x);
int x2 = Math.max(x + width, other.x + other.width);
int y1 = Math.min(y, other.y);
int y2 = Math.max(y + height, other.y + other.height);
return new Rectangle(x1, y1, x2 - x1, y2 - y1);
}
/** {@inheritDoc} */
public String toString ()
{
return getClass().getName() + "[x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]";
return getClass().getName() + "[x=" + x + ", y=" + y +
", width=" + width + ", height=" + height + "]";
}
}
@@ -6,9 +6,7 @@
package com.threerings.getdown.util;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* Tests {@link Color}.
@@ -16,96 +14,10 @@ import static org.junit.Assert.assertFalse;
public class ColorTest
{
@Test
public void testRgbConstructor ()
{
Color color1 = new Color(0, 0, 0);
assertEquals(0, color1.red);
assertEquals(0, color1.green);
assertEquals(0, color1.blue);
assertEquals(255, color1.alpha);
assertEquals(0xFF000000, color1.rgba());
assertEquals(0, color1.brightness(), 0.0000001);
assertEquals(Color.BLACK, color1);
Color color2 = new Color(255, 255, 255);
assertEquals(255, color2.red);
assertEquals(255, color2.green);
assertEquals(255, color2.blue);
assertEquals(255, color2.alpha);
assertEquals(0xFFFFFFFF, color2.rgba());
assertEquals(1, color2.brightness(), 0.0000001);
assertEquals(Color.WHITE, color2);
Color color3 = new Color(1, 2, 3);
assertEquals(1, color3.red);
assertEquals(2, color3.green);
assertEquals(3, color3.blue);
assertEquals(255, color3.alpha);
assertEquals(0xFF010203, color3.rgba());
assertEquals(0.0117647, color3.brightness(), 0.0000001);
Color color4 = new Color(-100, 300, 200);
assertEquals(0, color4.red);
assertEquals(255, color4.green);
assertEquals(200, color4.blue);
assertEquals(255, color4.alpha);
assertEquals(0xFF00FFC8, color4.rgba());
assertEquals(1, color4.brightness(), 0.0000001);
}
@Test
public void testRgbaConstructor ()
{
Color color1 = new Color(0, false);
assertEquals(0, color1.red);
assertEquals(0, color1.green);
assertEquals(0, color1.blue);
assertEquals(255, color1.alpha);
assertEquals(0xFF000000, color1.rgba());
assertEquals(0, color1.brightness(), 0.0000001);
assertEquals(Color.BLACK, color1);
Color color2 = new Color(0, true);
assertEquals(0, color2.red);
assertEquals(0, color2.green);
assertEquals(0, color2.blue);
assertEquals(0, color2.alpha);
assertEquals(0, color2.rgba());
assertEquals(0, color2.brightness(), 0.0000001);
assertFalse(Color.BLACK.equals(color2));
Color color3 = new Color(0x00FFFFFF, false);
assertEquals(255, color3.red);
assertEquals(255, color3.green);
assertEquals(255, color3.blue);
assertEquals(255, color3.alpha);
assertEquals(0xFFFFFFFF, color3.rgba());
assertEquals(1, color3.brightness(), 0.0000001);
assertEquals(Color.WHITE, color3);
Color color4 = new Color(0x00FFFFFF, true);
assertEquals(255, color4.red);
assertEquals(255, color4.green);
assertEquals(255, color4.blue);
assertEquals(0, color4.alpha);
assertEquals(0x00FFFFFF, color4.rgba());
assertEquals(1, color4.brightness(), 0.0000001);
assertFalse(Color.WHITE.equals(color4));
Color color5 = new Color(0x00010203, false);
assertEquals(1, color5.red);
assertEquals(2, color5.green);
assertEquals(3, color5.blue);
assertEquals(255, color5.alpha);
assertEquals(0xFF010203, color5.rgba());
assertEquals(0.0117647, color5.brightness(), 0.0000001);
Color color6 = new Color(0x00010203, true);
assertEquals(1, color6.red);
assertEquals(2, color6.green);
assertEquals(3, color6.blue);
assertEquals(0, color6.alpha);
assertEquals(0x00010203, color6.rgba());
assertEquals(0.0117647, color6.brightness(), 0.0000001);
public void testBrightness() {
assertEquals(0, Color.brightness(0xFF000000), 0.0000001);
assertEquals(1, Color.brightness(0xFFFFFFFF), 0.0000001);
assertEquals(0.0117647, Color.brightness(0xFF010203), 0.0000001);
assertEquals(1, Color.brightness(0xFF00FFC8), 0.0000001);
}
}