Created
September 7, 2024 21:32
-
-
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 file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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