Skip to content

Commit 1dd9705

Browse files
EC2 Default Userclaude
andcommitted
Add calibration tool for measuring actual circle sizes and alpha values
Provides a CalibrationPatternGenerator that creates a 6x6 reference grid (sizes x alphas) and a ScreenshotAnalyzer that measures painted circles from Rust screenshots to detect mismatches with hardcoded SIZES and ALPHAS constants. Includes grid auto-detection via brightness projections, shape diff image generation, and a copy-paste Java snippet for corrected values. Also makes CircleCache and Scanline public so the calibration package can access the scanline masks. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 53d494f commit 1dd9705

5 files changed

Lines changed: 959 additions & 2 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
package com.bobrust.calibration;
2+
3+
import com.bobrust.generator.BorstUtils;
4+
import com.bobrust.generator.CircleCache;
5+
import com.bobrust.generator.Scanline;
6+
7+
import javax.imageio.ImageIO;
8+
import java.awt.*;
9+
import java.awt.image.BufferedImage;
10+
import java.io.File;
11+
import java.io.IOException;
12+
13+
/**
14+
* Generates a calibration reference pattern for measuring actual circle sizes and alpha values
15+
* as painted by the Rust game.
16+
*
17+
* The pattern is a 6x6 grid: 6 circle sizes (columns) x 6 alpha values (rows).
18+
* All circles are white on a black background. The user paints this in Rust, takes a screenshot,
19+
* and feeds it to {@link ScreenshotAnalyzer}.
20+
*
21+
* Usage: java -cp ... com.bobrust.calibration.CalibrationPatternGenerator [output.png] [width] [height]
22+
*/
23+
public class CalibrationPatternGenerator {
24+
25+
/** Number of size levels (columns). */
26+
public static final int NUM_SIZES = BorstUtils.SIZES.length;
27+
/** Number of alpha levels (rows). */
28+
public static final int NUM_ALPHAS = BorstUtils.ALPHAS.length;
29+
30+
/** Padding around the entire grid in pixels. */
31+
public static final int GRID_PADDING = 8;
32+
/** Spacing between cell centers. Must be > largest circle diameter. */
33+
public static final int CELL_SPACING = 110;
34+
35+
/**
36+
* Generate the calibration pattern image.
37+
*
38+
* @param width image width (0 = auto-calculate)
39+
* @param height image height (0 = auto-calculate)
40+
* @return the generated calibration image
41+
*/
42+
public static BufferedImage generate(int width, int height) {
43+
int gridW = GRID_PADDING * 2 + CELL_SPACING * NUM_SIZES;
44+
int gridH = GRID_PADDING * 2 + CELL_SPACING * NUM_ALPHAS;
45+
46+
if (width <= 0) width = gridW;
47+
if (height <= 0) height = gridH;
48+
49+
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
50+
Graphics2D g = image.createGraphics();
51+
52+
// Black background
53+
g.setColor(Color.BLACK);
54+
g.fillRect(0, 0, width, height);
55+
56+
// Draw grid labels — use very dim color (below paint threshold of 10)
57+
// so they don't interfere with circle detection
58+
g.setColor(new Color(8, 8, 8));
59+
g.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 9));
60+
61+
// Column labels (sizes)
62+
for (int col = 0; col < NUM_SIZES; col++) {
63+
int cx = GRID_PADDING + CELL_SPACING / 2 + col * CELL_SPACING;
64+
String label = "s=" + BorstUtils.SIZES[col];
65+
FontMetrics fm = g.getFontMetrics();
66+
g.drawString(label, cx - fm.stringWidth(label) / 2, GRID_PADDING - 1);
67+
}
68+
69+
// Row labels (alphas)
70+
for (int row = 0; row < NUM_ALPHAS; row++) {
71+
int cy = GRID_PADDING + CELL_SPACING / 2 + row * CELL_SPACING;
72+
String label = "a=" + BorstUtils.ALPHAS[row];
73+
g.drawString(label, 1, cy + 3);
74+
}
75+
76+
g.dispose();
77+
78+
// Draw circles using the same scanline approach as the engine
79+
for (int row = 0; row < NUM_ALPHAS; row++) {
80+
int alpha = BorstUtils.ALPHAS[row];
81+
for (int col = 0; col < NUM_SIZES; col++) {
82+
int sizeIdx = col;
83+
int cx = GRID_PADDING + CELL_SPACING / 2 + col * CELL_SPACING;
84+
int cy = GRID_PADDING + CELL_SPACING / 2 + row * CELL_SPACING;
85+
86+
drawCircleScanlines(image, cx, cy, sizeIdx, alpha);
87+
}
88+
}
89+
90+
return image;
91+
}
92+
93+
/**
94+
* Draw a circle using the exact same scanline mask from CircleCache.
95+
*/
96+
private static void drawCircleScanlines(BufferedImage image, int cx, int cy, int sizeIdx, int alpha) {
97+
Scanline[] scanlines = CircleCache.CIRCLE_CACHE[sizeIdx];
98+
int w = image.getWidth();
99+
int h = image.getHeight();
100+
101+
// White with given alpha, composited onto black background:
102+
// result = alpha/255 * 255 = alpha
103+
int grey = alpha;
104+
int argb = (0xFF << 24) | (grey << 16) | (grey << 8) | grey;
105+
106+
for (Scanline sl : scanlines) {
107+
int py = cy + sl.y;
108+
if (py < 0 || py >= h) continue;
109+
for (int x = sl.x1; x <= sl.x2; x++) {
110+
int px = cx + x;
111+
if (px < 0 || px >= w) continue;
112+
image.setRGB(px, py, argb);
113+
}
114+
}
115+
}
116+
117+
/**
118+
* Get the center coordinates of a grid cell (col=sizeIndex, row=alphaIndex).
119+
*/
120+
public static int getCellCenterX(int col) {
121+
return GRID_PADDING + CELL_SPACING / 2 + col * CELL_SPACING;
122+
}
123+
124+
public static int getCellCenterY(int row) {
125+
return GRID_PADDING + CELL_SPACING / 2 + row * CELL_SPACING;
126+
}
127+
128+
public static void main(String[] args) throws IOException {
129+
String outputPath = args.length > 0 ? args[0] : "calibration_pattern.png";
130+
int width = args.length > 1 ? Integer.parseInt(args[1]) : 0;
131+
int height = args.length > 2 ? Integer.parseInt(args[2]) : 0;
132+
133+
BufferedImage image = generate(width, height);
134+
File outFile = new File(outputPath);
135+
ImageIO.write(image, "PNG", outFile);
136+
137+
System.out.println("Calibration pattern saved to: " + outFile.getAbsolutePath());
138+
System.out.println("Image size: " + image.getWidth() + " x " + image.getHeight());
139+
System.out.println();
140+
System.out.println("Grid layout: " + NUM_SIZES + " columns (sizes) x " + NUM_ALPHAS + " rows (alphas)");
141+
System.out.println("Sizes: " + java.util.Arrays.toString(BorstUtils.SIZES));
142+
System.out.println("Alphas: " + java.util.Arrays.toString(BorstUtils.ALPHAS));
143+
System.out.println();
144+
System.out.println("Next steps:");
145+
System.out.println(" 1. Use this image as the paint source in Bob-Rust (or paint manually in Rust)");
146+
System.out.println(" 2. Take a screenshot of the painted result on a Rust sign");
147+
System.out.println(" 3. Run: ScreenshotAnalyzer <screenshot.png>");
148+
}
149+
}

0 commit comments

Comments
 (0)