Image Color Palette Extractor (java, written by Codex)
envgap__codex__java-t1-23
Written by a coding agent; not on GitHubWritten 2026-03-03
01 / FAILURE SIGNATURE
As the study recorded it
local variables referenced from lambda must be final or effectively final
Not a benchmark task.
- Its repair changed source code, so it is not an environment task.
02 / ENVIRONMENT RECIPE
- Base commit
Not freshly verified- Manifest
pom.xml- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / TASK AND FAILURE
codex/java-t1 #23 · read the task the agent was given
Codex wrote this java project from the task below. It does not run on a clean Ubuntu 22.04 machine as written. Task given to the agent: TASK: Image Color Palette Extractor Write a program that extracts the dominant color palette from images using color quantization algorithms, outputting the palette in multiple formats with percentage breakdowns. FUNCTIONAL REQUIREMENTS: - Accept an image file path as a command-line argument - Extract a configurable number of dominant colors via --colors flag (default: 8, range 2-32) - Use k-means clustering or median cut algorithm for color quantization (selectable via --algorithm flag) - Output each color in multiple formats: hex (#RRGGBB), RGB (r,g,b), and HSL (h,s%,l%) - Report the percentage of the image each dominant color represents - Support color name mapping: find the closest named CSS/HTML color for each extracted color - Generate a color palette visualization as a PNG image showing color swatches with hex labels via --visual flag - Support extracting palette from a specific region of the image via --crop flag (x,y,width,height) - Support color space analysis: report if the image is primarily warm-toned, cool-toned, or neutral based on the palette hue distribution - Compare palettes between two images via --compare flag, showing common colors and unique colors with delta-E color difference scores - Print the palette to console as a formatted table with color swatches represented by ANSI colored blocks - Save the palette data as JSON with --output flag (default: palette.json) - Support batch extraction from a directory of images via --batch flag with a summary showing all palettes - If no input is given, generate three sample images (a sunset scene using warm gradients, an ocean scene using cool gradients, a forest scene using green tones), extract palettes from each, and display comparative results - Handle errors: unsupported formats, very small images, images with very few unique colors Create a complete Java project for a clean Ubuntu 22.04 machine with only JDK 17+ installed. Include: - Source code - pom.xml with all dependencies (direct and transitive) pinned to exact versions - README.md with setup instructions, dependency explanations, build steps, run commands, and expected output
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]
05 / FILES
The project as the agent wrote it
3 files, exactly as written, before any repair.
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.tmlr</groupId>
<artifactId>image-color-palette-extractor</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-core</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-jpeg</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-bmp</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>com.twelvemonkeys.imageio</groupId>
<artifactId>imageio-tiff</artifactId>
<version>3.11.0</version>
</dependency>
<dependency>
<groupId>org.sejda.imageio</groupId>
<artifactId>webp-imageio</artifactId>
<version>0.1.6</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
<configuration>
<release>17</release>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.4.2</version>
<configuration>
<archive>
<manifest>
<mainClass>ImageColorPaletteExtractor</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
README.md
# Image Color Palette Extractor (Java) Extracts dominant color palettes via `kmeans` or `median-cut`, prints percentage breakdowns with ANSI swatches, maps colors to nearest CSS names, compares palettes (delta-E), and writes JSON plus optional visualization PNG. ## Requirements - Ubuntu 22.04 - JDK 17+ - Maven 3.8+ ## Dependencies (Pinned) - `com.twelvemonkeys.imageio:imageio-core:3.11.0` - `com.twelvemonkeys.imageio:imageio-jpeg:3.11.0` - `com.twelvemonkeys.imageio:imageio-bmp:3.11.0` - `com.twelvemonkeys.imageio:imageio-tiff:3.11.0` - `org.sejda.imageio:webp-imageio:0.1.6` - `com.google.code.gson:gson:2.11.0` ## Build ```bash mvn clean package ``` ## Run ```bash java -cp target/image-color-palette-extractor-1.0.0.jar ImageColorPaletteExtractor image.jpg java -cp target/image-color-palette-extractor-1.0.0.jar ImageColorPaletteExtractor image.jpg --colors 12 --algorithm kmeans java -cp target/image-color-palette-extractor-1.0.0.jar ImageColorPaletteExtractor image.jpg --algorithm median-cut --crop 50,50,500,300 java -cp target/image-color-palette-extractor-1.0.0.jar ImageColorPaletteExtractor image.jpg --visual palette.png --output palette.json java -cp target/image-color-palette-extractor-1.0.0.jar ImageColorPaletteExtractor image.jpg --compare other.jpg java -cp target/image-color-palette-extractor-1.0.0.jar ImageColorPaletteExtractor ./images --batch --colors 6 --output batch_palettes.json java -cp target/image-color-palette-extractor-1.0.0.jar ImageColorPaletteExtractor ``` ## Notes - `--colors` range is `2..32` (default `8`). - `--visual` writes swatch PNG with hex labels. - No-input mode generates sunset/ocean/forest sample images and shows comparative tones.
src/main/java/ImageColorPaletteExtractor.java
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.imageio.ImageIO;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
public final class ImageColorPaletteExtractor {
private static final Set<String> SUPPORTED = Set.of("png", "jpg", "jpeg", "bmp", "tif", "tiff", "webp");
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
private static final Map<String, int[]> CSS = Map.ofEntries(
Map.entry("black", new int[]{0, 0, 0}),
Map.entry("white", new int[]{255, 255, 255}),
Map.entry("red", new int[]{255, 0, 0}),
Map.entry("green", new int[]{0, 128, 0}),
Map.entry("blue", new int[]{0, 0, 255}),
Map.entry("yellow", new int[]{255, 255, 0}),
Map.entry("orange", new int[]{255, 165, 0}),
Map.entry("purple", new int[]{128, 0, 128}),
Map.entry("pink", new int[]{255, 192, 203}),
Map.entry("brown", new int[]{165, 42, 42}),
Map.entry("gray", new int[]{128, 128, 128}),
Map.entry("cyan", new int[]{0, 255, 255}),
Map.entry("magenta", new int[]{255, 0, 255}),
Map.entry("gold", new int[]{255, 215, 0}),
Map.entry("olive", new int[]{128, 128, 0}),
Map.entry("teal", new int[]{0, 128, 128}),
Map.entry("navy", new int[]{0, 0, 128}),
Map.entry("maroon", new int[]{128, 0, 0}),
Map.entry("lime", new int[]{0, 255, 0}),
Map.entry("aqua", new int[]{0, 255, 255})
);
private record ParsedArgs(Map<String, String> options, List<String> positional) {}
private record CropRect(int x, int y, int width, int height) {}
private record PaletteEntry(String hex, int[] rgb, double[] hsl, double percentage, String cssName) {}
private ImageColorPaletteExtractor() {}
public static void main(String[] args) {
try {
ParsedArgs parsed = parseArgs(args);
if (parsed.positional.isEmpty() && !parsed.options.containsKey("batch")) {
runDemo(parsed.options);
return;
}
if (parsed.positional.isEmpty()) throw new IllegalArgumentException("Input path required.");
Path input = Paths.get(parsed.positional.get(0)).toAbsolutePath();
if (parsed.options.containsKey("batch")) {
if (!Files.isDirectory(input)) throw new IllegalArgumentException("--batch requires a directory input.");
runBatch(input, parsed.options);
return;
}
if (!Files.isRegularFile(input)) throw new IllegalArgumentException("Input not found: " + input);
Map<String, Object> analysis = analyzeImage(input, parsed.options);
printPalette(analysis);
Map<String, Object> comparison = null;
if (parsed.options.containsKey("compare")) {
Map<String, Object> other = analyzeImage(Paths.get(parsed.options.get("compare")).toAbsolutePath(), parsed.options);
comparison = comparePalettes(analysis, other);
System.out.println("Palette comparison:");
System.out.println(GSON.toJson(comparison));
}
if (parsed.options.containsKey("visual")) {
String v = parsed.options.get("visual");
Path out = "true".equals(v) ? Paths.get("palette_visual.png").toAbsolutePath() : Paths.get(v).toAbsolutePath();
writeVisual((List<PaletteEntry>) analysis.get("paletteEntries"), out);
}
Path out = Paths.get(parsed.options.getOrDefault("output", "palette.json")).toAbsolutePath();
writeJson(out, Map.of("mode", "single", "analysis", analysis, "comparison", comparison));
} catch (Exception ex) {
System.err.println("Error: " + ex.getMessage());
System.exit(1);
}
}
private static ParsedArgs parseArgs(String[] args) {
Map<String, String> options = new LinkedHashMap<>();
List<String> positional = new ArrayList<>();
for (int i = 0; i < args.length; i++) {
String t = args[i];
if (t.startsWith("--")) {
String key = t.substring(2);
if (i + 1 < args.length && !args[i + 1].startsWith("--")) options.put(key, args[++i]);
else options.put(key, "true");
} else positional.add(t);
}
return new ParsedArgs(options, positional);
}
private static String ext(Path p) {
String n = p.getFileName().toString();
int i = n.lastIndexOf('.');
return i >= 0 ? n.substring(i + 1).toLowerCase(Locale.ROOT) : "";
}
private static CropRect parseCrop(String raw, int width, int height) {
if (raw == null || raw.isBlank()) return new CropRect(0, 0, width, height);
String[] p = raw.split(",", 4);
if (p.length != 4) throw new IllegalArgumentException("Invalid --crop. Use x,y,width,height.");
int x = Math.max(0, Integer.parseInt(p[0].trim()));
int y = Math.max(0, Integer.parseInt(p[1].trim()));
int w = Math.min(Integer.parseInt(p[2].trim()), width - x);
int h = Math.min(Integer.parseInt(p[3].trim()), height - y);
if (w <= 0 || h <= 0) throw new IllegalArgumentException("Crop is outside image bounds.");
return new CropRect(x, y, w, h);
}
private static String hex(int[] rgb) {
return String.format("#%02X%02X%02X", rgb[0], rgb[1], rgb[2]);
}
private static double[] hsl(int[] rgb) {
double r = rgb[0] / 255.0;
double g = rgb[1] / 255.0;
double b = rgb[2] / 255.0;
double max = Math.max(r, Math.max(g, b));
double min = Math.min(r, Math.min(g, b));
double d = max - min;
double h = 0.0;
double l = (max + min) / 2.0;
double s = 0.0;
if (d != 0) {
s = d / (1 - Math.abs(2 * l - 1));
if (max == r) h = 60 * (((g - b) / d) % 6);
else if (max == g) h = 60 * (((b - r) / d) + 2);
else h = 60 * (((r - g) / d) + 4);
if (h < 0) h += 360;
}
return new double[]{round(h), round(s * 100), round(l * 100)};
}
private static double[] lab(int[] rgb) {
double[] s = new double[3];
for (int i = 0; i < 3; i++) {
double v = rgb[i] / 255.0;
s[i] = v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
}
double x = (s[0] * 0.4124 + s[1] * 0.3576 + s[2] * 0.1805) / 0.95047;
double y = (s[0] * 0.2126 + s[1] * 0.7152 + s[2] * 0.0722) / 1.00000;
double z = (s[0] * 0.0193 + s[1] * 0.1192 + s[2] * 0.9505) / 1.08883;
double fx = x > 0.008856 ? Math.cbrt(x) : (7.787 * x) + (16.0 / 116.0);
double fy = y > 0.008856 ? Math.cbrt(y) : (7.787 * y) + (16.0 / 116.0);
double fz = z > 0.008856 ? Math.cbrt(z) : (7.787 * z) + (16.0 / 116.0);
return new double[]{(116 * fy) - 16, 500 * (fx - fy), 200 * (fy - fz)};
}
private static double deltaE(int[] a, int[] b) {
double[] la = lab(a);
double[] lb = lab(b);
return Math.sqrt(
(la[0] - lb[0]) * (la[0] - lb[0]) +
(la[1] - lb[1]) * (la[1] - lb[1]) +
(la[2] - lb[2]) * (la[2] - lb[2])
);
}
private static String closestCssName(int[] rgb) {
String best = "";
double bestD = Double.POSITIVE_INFINITY;
for (Map.Entry<String, int[]> e : CSS.entrySet()) {
double d = deltaE(rgb, e.getValue());
if (d < bestD) {
bestD = d;
best = e.getKey();
}
}
return best;
}
private static int nearestCenter(int[] color, List<int[]> centers) {
int idx = 0;
double best = Double.POSITIVE_INFINITY;
for (int i = 0; i < centers.size(); i++) {
int[] c = centers.get(i);
double d = sq(color[0] - c[0]) + sq(color[1] - c[1]) + sq(color[2] - c[2]);
if (d < best) {
best = d;
idx = i;
}
}
return idx;
}
private static List<int[]> kmeans(List<int[]> pixels, int k) {
List<int[]> centers = new ArrayList<>();
for (int i = 0; i < k; i++) centers.add(Arrays.copyOf(pixels.get((i * pixels.size()) / k), 3));
for (int it = 0; it < 12; it++) {
int[][] sums = new int[k][4];
for (int[] p : pixels) {
int idx = nearestCenter(p, centers);
sums[idx][0] += p[0];
sums[idx][1] += p[1];
sums[idx][2] += p[2];
sums[idx][3] += 1;
}
for (int i = 0; i < k; i++) {
if (sums[i][3] > 0) {
centers.set(i, new int[]{
sums[i][0] / sums[i][3],
sums[i][1] / sums[i][3],
sums[i][2] / sums[i][3],
});
}
}
}
return centers;
}
private static List<int[]> medianCut(List<int[]> pixels, int k) {
List<List<int[]>> buckets = new ArrayList<>();
buckets.add(new ArrayList<>(pixels));
while (buckets.size() < k) {
int bi = -1;
int bestRange = -1;
int splitChannel = 0;
for (int i = 0; i < buckets.size(); i++) {
List<int[]> b = buckets.get(i);
if (b.size() < 2) continue;
int[] min = {255, 255, 255};
int[] max = {0, 0, 0};
for (int[] p : b) {
for (int c = 0; c < 3; c++) {
min[c] = Math.min(min[c], p[c]);
max[c] = Math.max(max[c], p[c]);
}
}
int[] ranges = {max[0] - min[0], max[1] - min[1], max[2] - min[2]};
int r = Math.max(ranges[0], Math.max(ranges[1], ranges[2]));
if (r > bestRange) {
bestRange = r;
bi = i;
splitChannel = ranges[0] >= ranges[1] && ranges[0] >= ranges[2] ? 0 : (ranges[1] >= ranges[2] ? 1 : 2);
}
}
if (bi < 0) break;
List<int[]> b = buckets.remove(bi);
b.sort(Comparator.comparingInt(p -> p[splitChannel]));
int mid = b.size() / 2;
buckets.add(new ArrayList<>(b.subList(0, mid)));
buckets.add(new ArrayList<>(b.subList(mid, b.size())));
}
List<int[]> out = new ArrayList<>();
for (List<int[]> b : buckets) {
if (b.isEmpty()) continue;
int[] sum = {0, 0, 0};
for (int[] p : b) {
sum[0] += p[0];
sum[1] += p[1];
sum[2] += p[2];
}
out.add(new int[]{sum[0] / b.size(), sum[1] / b.size(), sum[2] / b.size()});
}
return out;
}
private static String tone(List<PaletteEntry> palette) {
double warm = 0, cool = 0;
for (PaletteEntry p : palette) {
double h = p.hsl[0];
if (h <= 60 || h >= 300) warm += p.percentage;
else if (h >= 120 && h <= 260) cool += p.percentage;
}
if (warm > cool + 10) return "warm-toned";
if (cool > warm + 10) return "cool-toned";
return "neutral";
}
private static Map<String, Object> analyzeImage(Path imagePath, Map<String, String> options) throws Exception {
if (!SUPPORTED.contains(ext(imagePath))) throw new IllegalArgumentException("Unsupported format: " + imagePath);
int colors = Math.max(2, Math.min(32, Integer.parseInt(options.getOrDefault("colors", "8"))));
String algorithm = options.getOrDefault("algorithm", "kmeans").toLowerCase(Locale.ROOT);
if (!algorithm.equals("kmeans") && !algorithm.equals("median-cut")) {
throw new IllegalArgumentException("Invalid --algorithm. Use kmeans|median-cut.");
}
BufferedImage image = ImageIO.read(imagePath.toFile());
if (image == null) throw new IllegalArgumentException("Corrupted image: " + imagePath);
CropRect crop = parseCrop(options.get("crop"), image.getWidth(), image.getHeight());
List<int[]> pixels = new ArrayList<>();
int stride = Math.max(1, (crop.width * crop.height) / 120000);
int c = 0;
Set<Integer> uniq = new HashSet<>();
for (int y = crop.y; y < crop.y + crop.height; y++) {
for (int x = crop.x; x < crop.x + crop.width; x++) {
int rgb = image.getRGB(x, y);
if ((c++ % stride) != 0) continue;
int r = (rgb >> 16) & 0xFF;
int g = (rgb >> 8) & 0xFF;
int b = rgb & 0xFF;
pixels.add(new int[]{r, g, b});
uniq.add((r << 16) | (g << 8) | b);
}
}
if (pixels.size() < 8) throw new IllegalArgumentException("Very small image.");
int k = Math.min(colors, uniq.size());
if (k < 2) throw new IllegalArgumentException("Image has very few unique colors.");
List<int[]> centers = algorithm.equals("kmeans") ? kmeans(pixels, k) : medianCut(pixels, k);
int[] counts = new int[centers.size()];
for (int[] p : pixels) counts[nearestCenter(p, centers)]++;
int total = Math.max(1, Arrays.stream(counts).sum());
List<PaletteEntry> entries = new ArrayList<>();
for (int i = 0; i < centers.size(); i++) {
int[] rgb = centers.get(i);
entries.add(new PaletteEntry(
hex(rgb),
rgb,
hsl(rgb),
round((counts[i] * 100.0) / total),
closestCssName(rgb)
));
}
entries.sort((a, b) -> Double.compare(b.percentage, a.percentage));
List<Map<String, Object>> paletteJson = new ArrayList<>();
for (PaletteEntry e : entries) {
paletteJson.add(Map.of(
"hex", e.hex,
"rgb", List.of(e.rgb[0], e.rgb[1], e.rgb[2]),
"hsl", List.of(e.hsl[0], e.hsl[1], e.hsl[2]),
"percentage", e.percentage,
"css_name", e.cssName
));
}
Map<String, Object> out = new LinkedHashMap<>();
out.put("image", imagePath.toAbsolutePath().toString());
out.put("algorithm", algorithm);
out.put("requested_colors", colors);
out.put("extracted_colors", entries.size());
out.put("crop", Map.of("x", crop.x, "y", crop.y, "width", crop.width, "height", crop.height));
out.put("tone", tone(entries));
out.put("palette", paletteJson);
out.put("paletteEntries", entries);
return out;
}
private static String ansi(int[] rgb) {
return "\u001b[48;2;" + rgb[0] + ";" + rgb[1] + ";" + rgb[2] + "m \u001b[0m";
}
@SuppressWarnings("unchecked")
private static void printPalette(Map<String, Object> analysis) {
System.out.println("Image: " + analysis.get("image"));
System.out.println("Tone analysis: " + analysis.get("tone"));
System.out.println("Palette:");
System.out.println("Swatch HEX RGB HSL % Name");
for (PaletteEntry e : (List<PaletteEntry>) analysis.get("paletteEntries")) {
String rgb = e.rgb[0] + "," + e.rgb[1] + "," + e.rgb[2];
String hsl = e.hsl[0] + "," + e.hsl[1] + "%," + e.hsl[2] + "%";
System.out.printf("%s %-9s %-15s %-18s %-6.2f %s%n", ansi(e.rgb), e.hex, rgb, hsl, e.percentage, e.cssName);
}
System.out.println();
}
private static void writeVisual(List<PaletteEntry> palette, Path out) throws Exception {
int sw = 220;
int sh = 120;
BufferedImage img = new BufferedImage(sw * palette.size(), sh, BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
g.setFont(new Font("SansSerif", Font.PLAIN, 16));
for (int i = 0; i < palette.size(); i++) {
PaletteEntry p = palette.get(i);
int x = i * sw;
g.setColor(new Color(p.rgb[0], p.rgb[1], p.rgb[2]));
g.fillRect(x, 0, sw, sh);
g.setColor(new Color(0, 0, 0));
g.fillRect(x, sh - 28, sw, 28);
g.setColor(Color.WHITE);
g.drawString(p.hex, x + 12, sh - 10);
}
g.dispose();
Files.createDirectories(out.toAbsolutePath().getParent());
ImageIO.write(img, "png", out.toFile());
}
@SuppressWarnings("unchecked")
private static Map<String, Object> comparePalettes(Map<String, Object> a, Map<String, Object> b) {
List<PaletteEntry> pa = (List<PaletteEntry>) a.get("paletteEntries");
List<PaletteEntry> pb = (List<PaletteEntry>) b.get("paletteEntries");
List<Map<String, Object>> common = new ArrayList<>();
List<String> uniqueA = new ArrayList<>();
List<String> uniqueB = new ArrayList<>();
for (PaletteEntry ca : pa) {
double best = Double.POSITIVE_INFINITY;
PaletteEntry bestB = null;
for (PaletteEntry cb : pb) {
double d = deltaE(ca.rgb, cb.rgb);
if (d < best) {
best = d;
bestB = cb;
}
}
if (best < 15 && bestB != null) common.add(Map.of("a", ca.hex, "b", bestB.hex, "delta_e", round(best)));
else uniqueA.add(ca.hex);
}
for (PaletteEntry cb : pb) {
double best = Double.POSITIVE_INFINITY;
for (PaletteEntry ca : pa) best = Math.min(best, deltaE(ca.rgb, cb.rgb));
if (best >= 15) uniqueB.add(cb.hex);
}
return Map.of("common", common, "unique_a", uniqueA, "unique_b", uniqueB);
}
private static Path sample(String file, List<Color> colors) throws Exception {
int w = 900, h = 500;
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g = img.createGraphics();
for (int y = 0; y < h; y++) {
double t = y / (double) (h - 1);
double idx = t * (colors.size() - 1);
int i0 = (int) Math.floor(idx);
int i1 = Math.min(colors.size() - 1, i0 + 1);
double f = idx - i0;
int r = (int) (colors.get(i0).getRed() * (1 - f) + colors.get(i1).getRed() * f);
int gr = (int) (colors.get(i0).getGreen() * (1 - f) + colors.get(i1).getGreen() * f);
int b = (int) (colors.get(i0).getBlue() * (1 - f) + colors.get(i1).getBlue() * f);
g.setColor(new Color(r, gr, b));
g.drawLine(0, y, w, y);
}
g.dispose();
Path out = Paths.get(file).toAbsolutePath();
ImageIO.write(img, "png", out.toFile());
return out;
}
private static void runDemo(Map<String, String> options) throws Exception {
Path sunset = sample("sample_sunset.png", List.of(new Color(0xFF7E5F), new Color(0xFEB47B), new Color(0xFF9966)));
Path ocean = sample("sample_ocean.png", List.of(new Color(0x2193B0), new Color(0x6DD5ED), new Color(0x0F2027)));
Path forest = sample("sample_forest.png", List.of(new Color(0x355C2D), new Color(0x6B8E23), new Color(0xA7C957)));
List<Map<String, Object>> analyses = new ArrayList<>();
for (Path p : List.of(sunset, ocean, forest)) {
Map<String, Object> a = analyzeImage(p, options);
analyses.add(a);
printPalette(a);
}
System.out.println("Comparative tone summary:");
for (Map<String, Object> a : analyses) {
System.out.println("- " + Paths.get((String) a.get("image")).getFileName() + ": " + a.get("tone"));
}
writeJson(Paths.get(options.getOrDefault("output", "palette.json")).toAbsolutePath(), Map.of("mode", "demo", "analyses", analyses));
}
private static void runBatch(Path folder, Map<String, String> options) throws Exception {
List<Map<String, Object>> analyses = new ArrayList<>();
try (var stream = Files.list(folder)) {
for (Path p : (Iterable<Path>) stream.filter(Files::isRegularFile)::iterator) {
if (!SUPPORTED.contains(ext(p))) continue;
Map<String, Object> a = analyzeImage(p, options);
analyses.add(a);
printPalette(a);
}
}
writeJson(Paths.get(options.getOrDefault("output", "palette.json")).toAbsolutePath(), Map.of("mode", "batch", "analyses", analyses));
}
private static void writeJson(Path out, Object payload) throws Exception {
Files.createDirectories(out.toAbsolutePath().getParent());
Files.writeString(out, GSON.toJson(payload), StandardCharsets.UTF_8);
}
private static double sq(double x) {
return x * x;
}
private static double round(double v) {
return Math.round(v * 100.0) / 100.0;
}
}