Skip to content

Instantly share code, notes, and snippets.

@meew0
Created September 7, 2024 21:32
Show Gist options
  • Save meew0/dc4dcae1b6b2ea59fa5ef730270fba8c to your computer and use it in GitHub Desktop.
Save meew0/dc4dcae1b6b2ea59fa5ef730270fba8c to your computer and use it in GitHub Desktop.
Standalone Java tool to generate 3 representative colours with good contrast from an image, to be used respectively as a background colour, an accent colour, and a muted foreground text colour. Uses https://github.com/trickl/palette/ as a base.
/*
* This Java source file was generated by the Gradle 'init' task.
*/
package sa_image_palette;
import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.imageio.ImageIO;
import com.trickl.palette.ColorUtils;
import com.trickl.palette.Palette;
public class App {
private static float wraparoundDifference(float f0, float f1) {
f0 -= 180;
f1 -= 180;
float a = f1 - f0;
if (a > 180)
a -= 360;
if (a < -180)
a += 360;
return Math.abs(a);
}
public static class SwatchType {
public static final SwatchType DV = new SwatchType("DV", Palette::getDarkVibrantSwatch);
public static final SwatchType DM = new SwatchType("DM", Palette::getDarkMutedSwatch);
public static final SwatchType V = new SwatchType("V", Palette::getVibrantSwatch);
public static final SwatchType M = new SwatchType("M", Palette::getMutedSwatch);
public static final SwatchType LV = new SwatchType("LV", Palette::getLightVibrantSwatch);
public static final SwatchType LM = new SwatchType("LM", Palette::getLightMutedSwatch);
public static final SwatchType[] ALL = new SwatchType[] { DV, DM, V, M, LV, LM };
static {
DV.setPairings(LV, V);
DM.setPairings(LV, V);
V.setPairings(LV, DV, LM, DM);
M.setPairings(LV, DV, LM, DM);
LV.setPairings(DM, DV, M, V);
LM.setPairings(DV, DM, V, M);
}
private SwatchType[] pairings;
private final Function<Palette, Palette.Swatch> provider;
public final String name;
private void setPairings(SwatchType... pairings) {
this.pairings = pairings;
}
private SwatchType(String name, Function<Palette, Palette.Swatch> provider) {
this.name = name;
this.provider = provider;
}
public Palette.Swatch provide(Palette palette) {
return provider.apply(palette);
}
public static void sortByFrequency(final Palette palette, SwatchType[] input) {
Arrays.sort(input, new Comparator<SwatchType>() {
@Override
public int compare(SwatchType arg0, SwatchType arg1) {
Palette.Swatch s0 = arg0.provide(palette);
Palette.Swatch s1 = arg1.provide(palette);
if (s0 == null) {
if (s1 == null) {
return 0;
} else {
return 1;
}
} else if (s1 == null) {
return -1;
}
int p0 = s0.getPopulation();
int p1 = s1.getPopulation();
return Integer.compare(p1, p0);
}
});
}
public static void sortByHueContrast(final Palette palette, final Palette.Swatch compare, SwatchType[] input) {
float compareHue = compare.getHsl()[0];
Arrays.sort(input, new Comparator<SwatchType>() {
@Override
public int compare(SwatchType arg0, SwatchType arg1) {
Palette.Swatch s0 = arg0.provide(palette);
Palette.Swatch s1 = arg1.provide(palette);
if (s0 == null) {
if (s1 == null) {
return 0;
} else {
return 1;
}
} else if (s1 == null) {
return -1;
}
float hd0 = wraparoundDifference(s0.getHsl()[0], compareHue);
float hd1 = wraparoundDifference(s1.getHsl()[0], compareHue);
return Float.compare(hd1, hd0);
}
});
}
}
public static String getHexCode(Color color) {
if (color == null) {
throw new IllegalArgumentException("Color object cannot be null");
}
return String.format("#%02x%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue(), color.getAlpha());
}
public static SwatchType[] findBestPair(Palette palette, float minContrast) {
SwatchType.sortByFrequency(palette, SwatchType.ALL);
// Reduce contrast requirements for highly dominant colours
Palette.Swatch first = SwatchType.ALL[0].provide(palette), second = SwatchType.ALL[1].provide(palette);
if (second != null) {
if ((first.getPopulation()
/ second.getPopulation()) > 10)
minContrast -= 1.f;
if ((first.getPopulation()
/ second.getPopulation()) > 100)
minContrast -= 1.f;
}
for (SwatchType st : SwatchType.ALL) {
Palette.Swatch bg = st.provide(palette);
if (bg == null)
continue;
SwatchType.sortByHueContrast(palette, bg, st.pairings);
for (SwatchType pairing : st.pairings) {
Palette.Swatch fg = pairing.provide(palette);
if (fg == null)
continue;
if (ColorUtils.calculateContrast(fg.getColor(), bg.getColor()) >= minContrast) {
return new SwatchType[] { st, pairing };
}
}
}
return null;
}
public static void main(String[] args) {
String filePath = args[0];
BufferedImage image = null;
try {
// Read the image file into a BufferedImage
image = ImageIO.read(new File(filePath));
if (image == null) {
throw new RuntimeException("Invalid image");
} else {
Palette palette = Palette.from(image).generate();
SwatchType[] pair = findBestPair(palette, 4.5f);
if (pair == null)
pair = findBestPair(palette, 3.f);
if (pair == null) {
System.out.println(getHexCode(palette.getDominantSwatch().getColor()));
System.out.println(getHexCode(palette.getDominantSwatch().getBodyTextColor()));
System.out.println(getHexCode(palette.getDominantSwatch().getTitleTextColor()));
} else {
System.out.println(getHexCode(pair[0].provide(palette).getColor()));
System.out.println(getHexCode(pair[1].provide(palette).getColor()));
System.out.println(getHexCode(pair[0].provide(palette).getTitleTextColor()));
}
if (pair == null) {
System.out.println("null pair");
} else {
System.out.println(pair[0].name + " " + pair[1].name + "; " +
Arrays.stream(SwatchType.ALL)
.map(st -> st.name
+ (st.provide(palette) == null ? "x"
: (st.provide(palette).getPopulation() + "("
+ st.provide(palette).getHsl()[0] + ")")))
.collect(Collectors.joining("-")));
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment