From f4ec795ab10b389739acb911fd1a95214a73d003 Mon Sep 17 00:00:00 2001 From: EC2 Default User Date: Sat, 4 Apr 2026 21:16:53 +0000 Subject: [PATCH] Add error-guided circle placement with importance sampling Bias random circle placement toward high-error regions using a spatial error map with alias-table sampling. 80% of placements target high-error cells, 20% remain uniform for exploration. The error map updates incrementally after each shape, keeping overhead minimal. New files: - ErrorMap.java: spatial error grid with Vose alias method for O(1) sampling - ErrorGuidedPlacementTest.java: correctness tests and visual comparisons Feature flag: USE_ERROR_GUIDED_PLACEMENT (default true) in AppConstants. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../java/com/bobrust/generator/Circle.java | 20 +- .../java/com/bobrust/generator/ErrorMap.java | 233 ++++++++++++ .../bobrust/generator/HillClimbGenerator.java | 10 +- .../java/com/bobrust/generator/Model.java | 24 +- .../java/com/bobrust/generator/Worker.java | 11 + .../com/bobrust/util/data/AppConstants.java | 3 + .../generator/ErrorGuidedPlacementTest.java | 337 ++++++++++++++++++ test-results/edges_diff.png | Bin 0 -> 8635 bytes test-results/edges_guided_200shapes.png | Bin 0 -> 7286 bytes test-results/edges_target.png | Bin 0 -> 1733 bytes test-results/edges_uniform_200shapes.png | Bin 0 -> 7395 bytes test-results/nature_diff.png | Bin 0 -> 8514 bytes test-results/nature_guided_200shapes.png | Bin 0 -> 7253 bytes test-results/nature_target.png | Bin 0 -> 1220 bytes test-results/nature_uniform_200shapes.png | Bin 0 -> 7819 bytes test-results/photo_detail_diff.png | Bin 0 -> 7775 bytes .../photo_detail_guided_200shapes.png | Bin 0 -> 7190 bytes test-results/photo_detail_target.png | Bin 0 -> 25783 bytes .../photo_detail_uniform_200shapes.png | Bin 0 -> 6972 bytes 19 files changed, 629 insertions(+), 9 deletions(-) create mode 100644 src/main/java/com/bobrust/generator/ErrorMap.java create mode 100644 src/test/java/com/bobrust/generator/ErrorGuidedPlacementTest.java create mode 100644 test-results/edges_diff.png create mode 100644 test-results/edges_guided_200shapes.png create mode 100644 test-results/edges_target.png create mode 100644 test-results/edges_uniform_200shapes.png create mode 100644 test-results/nature_diff.png create mode 100644 test-results/nature_guided_200shapes.png create mode 100644 test-results/nature_target.png create mode 100644 test-results/nature_uniform_200shapes.png create mode 100644 test-results/photo_detail_diff.png create mode 100644 test-results/photo_detail_guided_200shapes.png create mode 100644 test-results/photo_detail_target.png create mode 100644 test-results/photo_detail_uniform_200shapes.png diff --git a/src/main/java/com/bobrust/generator/Circle.java b/src/main/java/com/bobrust/generator/Circle.java index 9aaf3cc..eb2cceb 100644 --- a/src/main/java/com/bobrust/generator/Circle.java +++ b/src/main/java/com/bobrust/generator/Circle.java @@ -41,9 +41,25 @@ public void mutateShape() { } public void randomize() { + randomize(null); + } + + /** + * Randomize circle position and size. + * When an {@link ErrorMap} is provided and error-guided placement is enabled, + * 80% of placements are biased toward high-error regions via importance + * sampling. The remaining 20% use uniform random placement for exploration. + */ + public void randomize(ErrorMap errorMap) { Random rnd = worker.getRandom(); - this.x = rnd.nextInt(worker.w); - this.y = rnd.nextInt(worker.h); + if (errorMap != null && rnd.nextFloat() < 0.8f) { + int[] pos = errorMap.samplePosition(rnd); + this.x = pos[0]; + this.y = pos[1]; + } else { + this.x = rnd.nextInt(worker.w); + this.y = rnd.nextInt(worker.h); + } this.r = BorstUtils.SIZES[rnd.nextInt(BorstUtils.SIZES.length)]; } diff --git a/src/main/java/com/bobrust/generator/ErrorMap.java b/src/main/java/com/bobrust/generator/ErrorMap.java new file mode 100644 index 0000000..d0c2e03 --- /dev/null +++ b/src/main/java/com/bobrust/generator/ErrorMap.java @@ -0,0 +1,233 @@ +package com.bobrust.generator; + +import java.util.Random; + +/** + * Spatial error map that tracks per-cell error across the image and supports + * importance sampling to bias circle placement toward high-error regions. + * + * The image is divided into a coarse grid (e.g. 32x32). Each cell stores the + * sum of squared per-pixel error for its region. An alias table enables O(1) + * weighted random sampling from the grid. + */ +public class ErrorMap { + private static final int DEFAULT_GRID_DIM = 32; + + final int gridWidth; + final int gridHeight; + final int cellWidth; + final int cellHeight; + final int imageWidth; + final int imageHeight; + final float[] cellErrors; + + // Alias table fields for O(1) weighted sampling + private int[] alias; + private float[] prob; + private boolean tableValid; + + public ErrorMap(int imageWidth, int imageHeight) { + this(imageWidth, imageHeight, DEFAULT_GRID_DIM, DEFAULT_GRID_DIM); + } + + public ErrorMap(int imageWidth, int imageHeight, int gridWidth, int gridHeight) { + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.gridWidth = gridWidth; + this.gridHeight = gridHeight; + this.cellWidth = Math.max(1, (imageWidth + gridWidth - 1) / gridWidth); + this.cellHeight = Math.max(1, (imageHeight + gridHeight - 1) / gridHeight); + this.cellErrors = new float[gridWidth * gridHeight]; + this.alias = new int[gridWidth * gridHeight]; + this.prob = new float[gridWidth * gridHeight]; + this.tableValid = false; + } + + /** + * Compute the full error map from scratch given target and current images. + */ + public void computeFull(BorstImage target, BorstImage current) { + int w = target.width; + int h = target.height; + int n = gridWidth * gridHeight; + for (int i = 0; i < n; i++) { + cellErrors[i] = 0; + } + + for (int py = 0; py < h; py++) { + int gy = py / cellHeight; + if (gy >= gridHeight) gy = gridHeight - 1; + int rowOffset = py * w; + for (int px = 0; px < w; px++) { + int gx = px / cellWidth; + if (gx >= gridWidth) gx = gridWidth - 1; + + int tt = target.pixels[rowOffset + px]; + int cc = current.pixels[rowOffset + px]; + + int dr = ((tt >>> 16) & 0xff) - ((cc >>> 16) & 0xff); + int dg = ((tt >>> 8) & 0xff) - ((cc >>> 8) & 0xff); + int db = (tt & 0xff) - (cc & 0xff); + + cellErrors[gy * gridWidth + gx] += dr * dr + dg * dg + db * db; + } + } + + tableValid = false; + } + + /** + * Incrementally update the error map after a circle was drawn. + * Only recomputes cells that overlap the circle's bounding box. + */ + public void updateIncremental(BorstImage target, BorstImage current, int cx, int cy, int cacheIndex) { + Scanline[] lines = CircleCache.CIRCLE_CACHE[cacheIndex]; + int w = target.width; + int h = target.height; + + // Find bounding box of affected grid cells + int minGx = Integer.MAX_VALUE, maxGx = Integer.MIN_VALUE; + int minGy = Integer.MAX_VALUE, maxGy = Integer.MIN_VALUE; + for (Scanline line : lines) { + int py = line.y + cy; + if (py < 0 || py >= h) continue; + int xs = Math.max(line.x1 + cx, 0); + int xe = Math.min(line.x2 + cx, w - 1); + if (xs > xe) continue; + + int gy = Math.min(py / cellHeight, gridHeight - 1); + int gx0 = Math.min(xs / cellWidth, gridWidth - 1); + int gx1 = Math.min(xe / cellWidth, gridWidth - 1); + + minGy = Math.min(minGy, gy); + maxGy = Math.max(maxGy, gy); + minGx = Math.min(minGx, gx0); + maxGx = Math.max(maxGx, gx1); + } + + if (minGx > maxGx || minGy > maxGy) return; + + // Recompute only the affected cells + for (int gy = minGy; gy <= maxGy; gy++) { + int pyStart = gy * cellHeight; + int pyEnd = Math.min(pyStart + cellHeight, h); + for (int gx = minGx; gx <= maxGx; gx++) { + int pxStart = gx * cellWidth; + int pxEnd = Math.min(pxStart + cellWidth, w); + + float error = 0; + for (int py = pyStart; py < pyEnd; py++) { + int rowOffset = py * w; + for (int px = pxStart; px < pxEnd; px++) { + int tt = target.pixels[rowOffset + px]; + int cc = current.pixels[rowOffset + px]; + + int dr = ((tt >>> 16) & 0xff) - ((cc >>> 16) & 0xff); + int dg = ((tt >>> 8) & 0xff) - ((cc >>> 8) & 0xff); + int db = (tt & 0xff) - (cc & 0xff); + + error += dr * dr + dg * dg + db * db; + } + } + cellErrors[gy * gridWidth + gx] = error; + } + } + + tableValid = false; + } + + /** + * Build the alias table for O(1) weighted sampling. + * Uses Vose's alias method. + */ + private void buildAliasTable() { + int n = cellErrors.length; + float totalError = 0; + for (int i = 0; i < n; i++) { + totalError += cellErrors[i]; + } + + if (totalError <= 0) { + // Uniform distribution fallback + for (int i = 0; i < n; i++) { + prob[i] = 1.0f; + alias[i] = i; + } + tableValid = true; + return; + } + + float[] scaled = new float[n]; + for (int i = 0; i < n; i++) { + scaled[i] = cellErrors[i] * n / totalError; + } + + // Partition into small and large + int[] small = new int[n]; + int[] large = new int[n]; + int smallCount = 0, largeCount = 0; + + for (int i = 0; i < n; i++) { + if (scaled[i] < 1.0f) { + small[smallCount++] = i; + } else { + large[largeCount++] = i; + } + } + + while (smallCount > 0 && largeCount > 0) { + int s = small[--smallCount]; + int l = large[--largeCount]; + + prob[s] = scaled[s]; + alias[s] = l; + + scaled[l] = (scaled[l] + scaled[s]) - 1.0f; + if (scaled[l] < 1.0f) { + small[smallCount++] = l; + } else { + large[largeCount++] = l; + } + } + + while (largeCount > 0) { + prob[large[--largeCount]] = 1.0f; + } + while (smallCount > 0) { + prob[small[--smallCount]] = 1.0f; + } + + tableValid = true; + } + + /** + * Sample a pixel position biased toward high-error regions. + * Uses the alias table for O(1) cell selection, then uniform + * random within the selected cell. + */ + public int[] samplePosition(Random rnd) { + if (!tableValid) { + buildAliasTable(); + } + + int n = cellErrors.length; + int cell; + int idx = rnd.nextInt(n); + if (rnd.nextFloat() < prob[idx]) { + cell = idx; + } else { + cell = alias[idx]; + } + + int gx = cell % gridWidth; + int gy = cell / gridWidth; + + int pxStart = gx * cellWidth; + int pyStart = gy * cellHeight; + + int px = pxStart + rnd.nextInt(Math.min(cellWidth, imageWidth - pxStart)); + int py = pyStart + rnd.nextInt(Math.min(cellHeight, imageHeight - pyStart)); + + return new int[]{px, py}; + } +} diff --git a/src/main/java/com/bobrust/generator/HillClimbGenerator.java b/src/main/java/com/bobrust/generator/HillClimbGenerator.java index 0723999..02bd2cd 100644 --- a/src/main/java/com/bobrust/generator/HillClimbGenerator.java +++ b/src/main/java/com/bobrust/generator/HillClimbGenerator.java @@ -6,12 +6,12 @@ import java.util.concurrent.ThreadLocalRandom; class HillClimbGenerator { - private static State getBestRandomState(List random_states) { + private static State getBestRandomState(List random_states, ErrorMap errorMap) { final int len = random_states.size(); for (int i = 0; i < len; i++) { State state = random_states.get(i); state.score = -1; - state.shape.randomize(); + state.shape.randomize(errorMap); } random_states.parallelStream().forEach(State::getEnergy); @@ -162,11 +162,15 @@ static float computeCoolingRate(float initialTemp, int maxAge) { } public static State getBestHillClimbState(List random_states, int age, int times) { + return getBestHillClimbState(random_states, age, times, null); + } + + public static State getBestHillClimbState(List random_states, int age, int times, ErrorMap errorMap) { float bestEnergy = 0; State bestState = null; for (int i = 0; i < times; i++) { - State oldState = getBestRandomState(random_states); + State oldState = getBestRandomState(random_states, errorMap); State state = getHillClimb(oldState, age); float energy = state.getEnergy(); diff --git a/src/main/java/com/bobrust/generator/Model.java b/src/main/java/com/bobrust/generator/Model.java index 4b76c55..d02255b 100644 --- a/src/main/java/com/bobrust/generator/Model.java +++ b/src/main/java/com/bobrust/generator/Model.java @@ -1,5 +1,7 @@ package com.bobrust.generator; +import com.bobrust.util.data.AppConstants; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -21,6 +23,8 @@ public class Model { public final int height; protected float score; + private ErrorMap errorMap; + public Model(BorstImage target, int backgroundRGB, int alpha) { int w = target.width; int h = target.height; @@ -33,11 +37,18 @@ public Model(BorstImage target, int backgroundRGB, int alpha) { this.current = new BorstImage(w, h); Arrays.fill(this.current.pixels, backgroundRGB); this.beforeImage = new BorstImage(w, h); - + this.score = BorstCore.differenceFull(target, current); this.context = new BorstImage(w, h); this.worker = new Worker(target, alpha); this.alpha = alpha; + + // Initialize error map if error-guided placement is enabled + if (AppConstants.USE_ERROR_GUIDED_PLACEMENT) { + this.errorMap = new ErrorMap(w, h); + this.errorMap.computeFull(target, current); + this.worker.setErrorMap(this.errorMap); + } } private void addShape(Circle shape) { @@ -45,13 +56,18 @@ private void addShape(Circle shape) { int cache_index = BorstUtils.getClosestSizeIndex(shape.r); BorstColor color = BorstCore.computeColor(target, current, alpha, cache_index, shape.x, shape.y); - + BorstCore.drawLines(current, color, alpha, cache_index, shape.x, shape.y); this.score = BorstCore.differencePartial(target, beforeImage, current, score, cache_index, shape.x, shape.y); shapes.add(shape); colors.add(color); - + BorstCore.drawLines(context, color, alpha, cache_index, shape.x, shape.y); + + // Incrementally update the error map after drawing the new shape + if (errorMap != null) { + errorMap.updateIncremental(target, current, shape.x, shape.y, cache_index); + } } private static final int max_random_states = 1000; @@ -69,7 +85,7 @@ public int processStep() { } } - State state = HillClimbGenerator.getBestHillClimbState(randomStates, age, times); + State state = HillClimbGenerator.getBestHillClimbState(randomStates, age, times, errorMap); addShape(state.shape); return worker.getCounter(); diff --git a/src/main/java/com/bobrust/generator/Worker.java b/src/main/java/com/bobrust/generator/Worker.java index a6e4ec6..6179162 100644 --- a/src/main/java/com/bobrust/generator/Worker.java +++ b/src/main/java/com/bobrust/generator/Worker.java @@ -13,6 +13,7 @@ class Worker { public final int h; public float score; private final AtomicInteger counter = new AtomicInteger(); + private ErrorMap errorMap; public Worker(BorstImage target, int alpha) { this.w = target.width; @@ -21,6 +22,16 @@ public Worker(BorstImage target, int alpha) { this.alpha = alpha; } + /** Returns the error map, or null if error-guided placement is disabled. */ + public ErrorMap getErrorMap() { + return errorMap; + } + + /** Sets the error map (called from Model when error-guided placement is enabled). */ + public void setErrorMap(ErrorMap errorMap) { + this.errorMap = errorMap; + } + /** * Returns a thread-local Random instance for use in parallel operations. * This avoids lock contention on a shared Random instance. diff --git a/src/main/java/com/bobrust/util/data/AppConstants.java b/src/main/java/com/bobrust/util/data/AppConstants.java index 6867178..57cf578 100644 --- a/src/main/java/com/bobrust/util/data/AppConstants.java +++ b/src/main/java/com/bobrust/util/data/AppConstants.java @@ -27,6 +27,9 @@ public interface AppConstants { // When true, use simulated annealing instead of pure hill climbing for shape optimization boolean USE_SIMULATED_ANNEALING = true; + + // When true, bias random circle placement toward high-error regions using importance sampling + boolean USE_ERROR_GUIDED_PLACEMENT = true; // Average canvas colors. Used as default colors Color CANVAS_AVERAGE = new Color(0xb3aba0); diff --git a/src/test/java/com/bobrust/generator/ErrorGuidedPlacementTest.java b/src/test/java/com/bobrust/generator/ErrorGuidedPlacementTest.java new file mode 100644 index 0000000..4a9d8c3 --- /dev/null +++ b/src/test/java/com/bobrust/generator/ErrorGuidedPlacementTest.java @@ -0,0 +1,337 @@ +package com.bobrust.generator; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import javax.imageio.ImageIO; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for the error-guided circle placement feature. + * + * Compares uniform random placement against error-guided placement to verify + * that biasing toward high-error regions produces equal or better results. + * Generates visual comparison images saved to build/test-output/. + */ +class ErrorGuidedPlacementTest { + private static final int ALPHA = 128; + private static final int BACKGROUND = 0xFFFFFFFF; + private static final File OUTPUT_DIR = new File("build/test-output"); + + @BeforeAll + static void setup() { + OUTPUT_DIR.mkdirs(); + } + + // ---- Core test runner ---- + + /** + * Run the generator for a given number of shapes. + * @param useErrorGuided if true, uses error-guided placement; if false, uniform random. + * @return the final Model (with score and rendered current image). + */ + private static Model runGenerator(BufferedImage testImage, int maxShapes, boolean useErrorGuided) { + BufferedImage argbImage = ensureArgb(testImage); + BorstImage target = new BorstImage(argbImage); + Model model = new Model(target, BACKGROUND, ALPHA); + + // Override the error map on the worker based on what we want to test + Worker worker = getWorker(model); + ErrorMap errorMap = useErrorGuided ? getErrorMap(model) : null; + if (!useErrorGuided) { + worker.setErrorMap(null); + setErrorMap(model, null); + } + + for (int i = 0; i < maxShapes; i++) { + worker.init(model.current, model.score); + List randomStates = createRandomStates(worker, 200); + State best = getBestRandomState(randomStates, errorMap); + State state = HillClimbGenerator.getHillClimbClassic(best, 100); + addShapeToModel(model, state.shape); + // Re-fetch the error map as it gets updated after addShape + if (useErrorGuided) { + errorMap = getErrorMap(model); + } + } + return model; + } + + // ---- Test: error-guided produces lower or equal error ---- + + @Test + void testErrorGuidedProducesLowerOrEqualError() { + BufferedImage testImage = TestImageGenerator.createPhotoDetail(); + int maxShapes = 50; + + Model uniformModel = runGenerator(testImage, maxShapes, false); + Model guidedModel = runGenerator(testImage, maxShapes, true); + + float uniformScore = uniformModel.score; + float guidedScore = guidedModel.score; + + System.out.println("Uniform score: " + uniformScore); + System.out.println("Guided score: " + guidedScore); + float improvement = (uniformScore - guidedScore) / uniformScore * 100; + System.out.println("Improvement: " + improvement + "%"); + + // Allow 5% tolerance for stochastic variation + assertTrue(guidedScore <= uniformScore * 1.05f, + "Guided score (" + guidedScore + ") should not be significantly worse than uniform (" + uniformScore + ")"); + } + + @Test + void testErrorGuidedNeverSignificantlyWorse() { + BufferedImage[] images = { + TestImageGenerator.createSolid(), + TestImageGenerator.createGradient(), + TestImageGenerator.createEdges(), + TestImageGenerator.createNature(), + }; + String[] names = {"solid", "gradient", "edges", "nature"}; + int maxShapes = 30; + + for (int idx = 0; idx < images.length; idx++) { + Model uniformModel = runGenerator(images[idx], maxShapes, false); + Model guidedModel = runGenerator(images[idx], maxShapes, true); + System.out.println(names[idx] + " — Uniform: " + uniformModel.score + ", Guided: " + guidedModel.score); + + assertTrue(guidedModel.score <= uniformModel.score * 1.05f, + names[idx] + ": Guided (" + guidedModel.score + ") should not be significantly worse than uniform (" + uniformModel.score + ")"); + } + } + + // ---- Test: ErrorMap correctness ---- + + @Test + void testErrorMapBasicCorrectness() { + BufferedImage img = new BufferedImage(64, 64, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + g.setColor(Color.RED); + g.fillRect(0, 0, 32, 64); // left half red + g.setColor(Color.WHITE); + g.fillRect(32, 0, 32, 64); // right half white + g.dispose(); + + BorstImage target = new BorstImage(ensureArgb(img)); + BorstImage current = new BorstImage(64, 64); + Arrays.fill(current.pixels, 0xFFFFFFFF); // all white + + ErrorMap map = new ErrorMap(64, 64, 2, 1); // 2 columns, 1 row + map.computeFull(target, current); + + // Left cells should have much higher error (red vs white) + // Right cells should have near-zero error (white vs white) + float leftError = map.cellErrors[0]; + float rightError = map.cellErrors[1]; + + assertTrue(leftError > rightError * 10, + "Left (red vs white) error (" + leftError + ") should be much higher than right (white vs white) error (" + rightError + ")"); + } + + @Test + void testErrorMapSamplingBias() { + // Create an image that is white everywhere except a small red patch + BufferedImage img = new BufferedImage(128, 128, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = img.createGraphics(); + g.setColor(Color.WHITE); + g.fillRect(0, 0, 128, 128); + g.setColor(Color.RED); + g.fillRect(0, 0, 32, 32); // top-left corner is red + g.dispose(); + + BorstImage target = new BorstImage(ensureArgb(img)); + BorstImage current = new BorstImage(128, 128); + Arrays.fill(current.pixels, 0xFFFFFFFF); + + ErrorMap map = new ErrorMap(128, 128); + map.computeFull(target, current); + + // Sample many positions and verify bias toward top-left + java.util.Random rnd = new java.util.Random(42); + int topLeftCount = 0; + int totalSamples = 10000; + for (int i = 0; i < totalSamples; i++) { + int[] pos = map.samplePosition(rnd); + if (pos[0] < 32 && pos[1] < 32) { + topLeftCount++; + } + } + + // The top-left quadrant is 1/16 of the image area but has almost all the error. + // With error-guided sampling, it should receive >50% of samples. + float topLeftFraction = topLeftCount / (float) totalSamples; + assertTrue(topLeftFraction > 0.5f, + "Error-guided sampling should heavily favor the high-error region, but only " + + (topLeftFraction * 100) + "% of samples hit the top-left corner"); + } + + // ---- Visual comparison benchmark ---- + + @Test + void testVisualComparison() throws IOException { + String[] names = {"photo_detail", "nature", "edges"}; + BufferedImage[] images = { + TestImageGenerator.createPhotoDetail(), + TestImageGenerator.createNature(), + TestImageGenerator.createEdges(), + }; + int maxShapes = 200; + + for (int idx = 0; idx < names.length; idx++) { + String name = names[idx]; + BufferedImage targetImg = images[idx]; + System.out.println("Generating visual comparison for: " + name); + + // Save target + ImageIO.write(targetImg, "png", new File(OUTPUT_DIR, name + "_target.png")); + + // Run both methods + Model uniformModel = runGenerator(targetImg, maxShapes, false); + Model guidedModel = runGenerator(targetImg, maxShapes, true); + + // Save rendered results + BufferedImage uniformResult = toBufferedImage(uniformModel.current); + BufferedImage guidedResult = toBufferedImage(guidedModel.current); + ImageIO.write(uniformResult, "png", new File(OUTPUT_DIR, name + "_uniform_200shapes.png")); + ImageIO.write(guidedResult, "png", new File(OUTPUT_DIR, name + "_guided_200shapes.png")); + + // Generate difference heatmap between the two results + BufferedImage diffImage = generateDiffHeatmap(uniformResult, guidedResult); + ImageIO.write(diffImage, "png", new File(OUTPUT_DIR, name + "_diff.png")); + + System.out.println(" Uniform score: " + uniformModel.score); + System.out.println(" Guided score: " + guidedModel.score); + float improvement = (uniformModel.score - guidedModel.score) / uniformModel.score * 100; + System.out.println(" Improvement: " + improvement + "%"); + } + } + + // ---- Helper methods ---- + + private static BufferedImage ensureArgb(BufferedImage img) { + if (img.getType() == BufferedImage.TYPE_INT_ARGB) return img; + BufferedImage argb = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g = argb.createGraphics(); + g.drawImage(img, 0, 0, null); + g.dispose(); + return argb; + } + + private static BufferedImage toBufferedImage(BorstImage borstImage) { + int w = borstImage.width; + int h = borstImage.height; + BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + int[] destPixels = ((java.awt.image.DataBufferInt) img.getRaster().getDataBuffer()).getData(); + System.arraycopy(borstImage.pixels, 0, destPixels, 0, borstImage.pixels.length); + return img; + } + + /** + * Generate a heatmap showing absolute difference between two images. + * Brighter = more different. + */ + private static BufferedImage generateDiffHeatmap(BufferedImage a, BufferedImage b) { + int w = a.getWidth(); + int h = a.getHeight(); + BufferedImage diff = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int ca = a.getRGB(x, y); + int cb = b.getRGB(x, y); + + int dr = Math.abs(((ca >> 16) & 0xff) - ((cb >> 16) & 0xff)); + int dg = Math.abs(((ca >> 8) & 0xff) - ((cb >> 8) & 0xff)); + int db = Math.abs((ca & 0xff) - (cb & 0xff)); + + // Scale up for visibility and map to a heat color + int intensity = Math.min(255, (dr + dg + db) * 2); + int heatR = Math.min(255, intensity * 2); + int heatG = Math.max(0, 255 - intensity * 2); + int heatB = 0; + + diff.setRGB(x, y, 0xFF000000 | (heatR << 16) | (heatG << 8) | heatB); + } + } + return diff; + } + + // ---- Reflective helpers ---- + + private static Worker getWorker(Model model) { + try { + Field field = Model.class.getDeclaredField("worker"); + field.setAccessible(true); + return (Worker) field.get(model); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static ErrorMap getErrorMap(Model model) { + try { + Field field = Model.class.getDeclaredField("errorMap"); + field.setAccessible(true); + return (ErrorMap) field.get(model); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void setErrorMap(Model model, ErrorMap errorMap) { + try { + Field field = Model.class.getDeclaredField("errorMap"); + field.setAccessible(true); + field.set(model, errorMap); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static List createRandomStates(Worker worker, int count) { + List states = new ArrayList<>(); + for (int i = 0; i < count; i++) { + states.add(new State(worker)); + } + return states; + } + + private static State getBestRandomState(List states, ErrorMap errorMap) { + for (State s : states) { + s.score = -1; + s.shape.randomize(errorMap); + } + states.parallelStream().forEach(State::getEnergy); + float bestEnergy = Float.MAX_VALUE; + State bestState = null; + for (State s : states) { + float energy = s.getEnergy(); + if (bestState == null || energy < bestEnergy) { + bestEnergy = energy; + bestState = s; + } + } + return bestState; + } + + private static void addShapeToModel(Model model, Circle shape) { + try { + Method method = Model.class.getDeclaredMethod("addShape", Circle.class); + method.setAccessible(true); + method.invoke(model, shape); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/test-results/edges_diff.png b/test-results/edges_diff.png new file mode 100644 index 0000000000000000000000000000000000000000..e97792fa68454367807e33155a0a62ba5479bf22 GIT binary patch literal 8635 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 literal 0 HcmV?d00001 diff --git a/test-results/edges_target.png b/test-results/edges_target.png new file mode 100644 index 0000000000000000000000000000000000000000..3b89f77eedc9fff9df3f722412bb203ac5daccb4 GIT binary patch literal 1733 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 Rj67a~ZJz!fkKDtO{|`Ht8}a}E literal 0 HcmV?d00001 diff --git a/test-results/nature_diff.png b/test-results/nature_diff.png new file mode 100644 index 0000000000000000000000000000000000000000..fb5f5af0c83a6b233ceea71b2e4a9f53f21c740c GIT binary patch literal 8514 zcmXw9c|25I+&_0_jG2%@WH*vjvPHHSLqgfxRK%2>CPihN7)!JX+1HsKT0F9pEoB+m zm$664Qo~TmU=(A0=Xu}H`^UZK-gD1>e#`lOfA^uCjk&-s$z1>dfm0TyXSgZ(zYB%r z-WzdOI{}bKpE5N*7vwyXBana3#kocBq`lQ1k!Dj3`#t|Xo$OP$-_vZ$ebLZ3(fEJ4 zdqhtD_woOellEVi^j`IB8Lo0lDqOldYiDgLTlG9CwZomOoHuE;e{rj_+jGjh1w&Vo zCaY{Gmz85isb}ZokZ*VDjeaEmwGHn%mA} z=VxHB*(4|}i(A}IT3Z;N2nbrsDKRu4wW?(=uYHe~mBp>qH8xU6BR`IJb3Xgw3^o^I zZN3}6+srp#r?3NKWBu0Xjg6k0ou??PIZTCne9P-UF?2kZ{PR5r&rAXsU)@=CJO49Z+pUs`;N5nbA`jsJP+WHF)ZR_!5 z*wKk}fBxCaL#hl52RJnq&tl9O+ zC~QaysLQIO5D|)V8D=o@lFh#0=Ibz`AAU>1

j1d`f0+;B4aDjHnI%^b+BQQBQI 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 OfKw-JOp8qjk^cjY(Skhy literal 0 HcmV?d00001 diff --git a/test-results/nature_guided_200shapes.png b/test-results/nature_guided_200shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..bad12f242332ead831d5679576d48ea246eeaeee GIT binary patch literal 7253 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 literal 0 HcmV?d00001 diff --git a/test-results/nature_target.png b/test-results/nature_target.png new file mode 100644 index 0000000000000000000000000000000000000000..674423b31bf68b66007081c6b118414509e6bc09 GIT binary patch literal 1220 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 CvMq`L literal 0 HcmV?d00001 diff --git a/test-results/photo_detail_diff.png b/test-results/photo_detail_diff.png new file mode 100644 index 0000000000000000000000000000000000000000..7031885383d77b0703c6c5eb538d6defd1881e13 GIT binary patch literal 7775 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 literal 0 HcmV?d00001 diff --git a/test-results/photo_detail_guided_200shapes.png b/test-results/photo_detail_guided_200shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..3a9783af63e9dd3375a401acdac2d855fe8cef23 GIT binary patch literal 7190 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 literal 0 HcmV?d00001 diff --git a/test-results/photo_detail_target.png b/test-results/photo_detail_target.png new file mode 100644 index 0000000000000000000000000000000000000000..9065aaf043a5a0c2e015853744db601f67377c78 GIT binary patch literal 25783 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 bEvo4Q1>yku6+Pk)N&cszpebJ{YZmx_CgBha literal 0 HcmV?d00001 diff --git a/test-results/photo_detail_uniform_200shapes.png b/test-results/photo_detail_uniform_200shapes.png new file mode 100644 index 0000000000000000000000000000000000000000..21395a4c29feeb0f2812be56ba194ce1f5999d43 GIT binary patch literal 6972 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