From 2e9db0e9d3753aa9733d3af5eb4c5c5ba75c1caa Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 25 Mar 2025 09:51:28 +0100 Subject: [PATCH 01/29] Update README.md --- app/src/processing/app/syntax/README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/processing/app/syntax/README.md b/app/src/processing/app/syntax/README.md index 04e7bdc328..c9af085996 100644 --- a/app/src/processing/app/syntax/README.md +++ b/app/src/processing/app/syntax/README.md @@ -1,4 +1,14 @@ -# 🐉 Fixing this code: here be dragons. 🐉 +# Replacing our custom version of JEditTextArea + +As we have started the migration to Jetpack Compose we will eventually need to replace the JEditTextArea as well. + +I think a good current strategy would be to start using `RSyntaxTextArea` for the upcoming p5.js mode as it is a better maintained well rounded library. As noted below, a lot of the current state management of the PDE is interetwined with the JEditTextArea implementation. This will force us to decouple the state management out of the `JEditTextArea` whilst also trying to keep backwards compatibility alive for Tweak Mode and the current implementation of autocomplete. + +I also did some more research into the potential of using a JS + LSP based editor within the Jetpack Compose but as of writing (early 2025) the only way to do so would be to embed chromium into the PDE through something like [Java-CEF]([url](https://github.com/chromiumembedded/java-cef)) and it looks like a PoC for Jetpack Compose Desktop exists [here](https://github.com/JetBrains/compose-multiplatform/blob/9cd413a4ed125bee5b624550fbd40a05061e912a/experimental/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt). Moving the entire PDE into an electron app would be essentially a rewrite which currrently is not the target. + +Research needs to be done on how much the Tweak Mode and autocompletion are _actually_ being used. Currently both these features are quite hidden and I suspect that most users actually move on to more advanced use-cases before they even discover such things. I would like to make both of these features much more prominent within the PDE to test if they are a good value add. + +### Ben Fry's notes Every few years, we've looked at replacing this package with [RSyntaxArea](https://github.com/bobbylight/RSyntaxTextArea), most recently with two attempts during the course of developing [Processing 4](https://github.com/processing/processing4/wiki/Processing-4), but probably dating back to the mid-2000s. From 9e241c7008b7925279f96531252d7166c3c9aa7b Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 16 Sep 2025 11:16:58 +0530 Subject: [PATCH 02/29] added the image comparator which is the pixel matching algorithm --- .../src/main/java/ImageComparator.java | 415 ++++++++++++++++++ 1 file changed, 415 insertions(+) create mode 100644 visual-tests/src/main/java/ImageComparator.java diff --git a/visual-tests/src/main/java/ImageComparator.java b/visual-tests/src/main/java/ImageComparator.java new file mode 100644 index 0000000000..db3eeab971 --- /dev/null +++ b/visual-tests/src/main/java/ImageComparator.java @@ -0,0 +1,415 @@ +import processing.core.*; +import java.util.*; + +class ComparisonResult { + public boolean passed; + public double mismatchRatio; + public boolean isFirstRun; + public PImage diffImage; + public ComparisonDetails details; + + public ComparisonResult(boolean passed, double mismatchRatio) { + this.passed = passed; + this.mismatchRatio = mismatchRatio; + this.isFirstRun = false; + } + + public ComparisonResult(boolean passed, PImage diffImage, ComparisonDetails details) { + this.passed = passed; + this.diffImage = diffImage; + this.details = details; + this.mismatchRatio = details != null ? (double) details.significantDiffPixels / (diffImage.width * diffImage.height) : 0.0; + this.isFirstRun = false; + } + + public static ComparisonResult createFirstRun() { + ComparisonResult result = new ComparisonResult(false, 0.0); + result.isFirstRun = true; + return result; + } + + public void saveDiffImage(String filePath) { + if (diffImage != null) { + diffImage.save(filePath); + System.out.println("Diff image saved: " + filePath); + } + } +} + +class ComparisonDetails { + public int totalDiffPixels; + public int significantDiffPixels; + public List clusters; + + public ComparisonDetails(int totalDiffPixels, int significantDiffPixels, List clusters) { + this.totalDiffPixels = totalDiffPixels; + this.significantDiffPixels = significantDiffPixels; + this.clusters = clusters; + } + + public void printDetails() { + System.out.println(" Total diff pixels: " + totalDiffPixels); + System.out.println(" Significant diff pixels: " + significantDiffPixels); + System.out.println(" Clusters found: " + clusters.size()); + + long lineShiftClusters = clusters.stream().filter(c -> c.isLineShift).count(); + if (lineShiftClusters > 0) { + System.out.println(" Line shift clusters (ignored): " + lineShiftClusters); + } + + // Print cluster details + for (int i = 0; i < clusters.size(); i++) { + ClusterInfo cluster = clusters.get(i); + System.out.println(" Cluster " + (i+1) + ": size=" + cluster.size + + ", lineShift=" + cluster.isLineShift); + } + } +} + +// Individual cluster information +class ClusterInfo { + public int size; + public List pixels; + public boolean isLineShift; + + public ClusterInfo(int size, List pixels, boolean isLineShift) { + this.size = size; + this.pixels = pixels; + this.isLineShift = isLineShift; + } +} + +// Simple 2D point +class Point2D { + public int x, y; + + public Point2D(int x, int y) { + this.x = x; + this.y = y; + } +} + +// Interface for pixel matching algorithms +interface PixelMatchingAlgorithm { + ComparisonResult compare(PImage baseline, PImage actual, double threshold); +} + +// Your sophisticated pixel matching algorithm +public class ImageComparator implements PixelMatchingAlgorithm { + + // Algorithm constants + private static final int MAX_SIDE = 400; + private static final int BG_COLOR = 0xFFFFFFFF; // White background + private static final int MIN_CLUSTER_SIZE = 4; + private static final int MAX_TOTAL_DIFF_PIXELS = 40; + private static final double DEFAULT_THRESHOLD = 0.5; + private static final double ALPHA = 0.1; + + private PApplet p; // Reference to PApplet for PImage creation + + public ImageComparator(PApplet p) { + this.p = p; + } + + @Override + public ComparisonResult compare(PImage baseline, PImage actual, double threshold) { + if (baseline == null || actual == null) { + return new ComparisonResult(false, 1.0); + } + + try { + return performComparison(baseline, actual, threshold); + } catch (Exception e) { + System.err.println("Comparison failed: " + e.getMessage()); + return new ComparisonResult(false, 1.0); + } + } + + private ComparisonResult performComparison(PImage baseline, PImage actual, double threshold) { + // Calculate scaling + double scale = Math.min( + (double) MAX_SIDE / baseline.width, + (double) MAX_SIDE / baseline.height + ); + + double ratio = (double) baseline.width / baseline.height; + boolean narrow = ratio != 1.0; + if (narrow) { + scale *= 2; + } + + // Resize images + PImage scaledActual = resizeImage(actual, scale); + PImage scaledBaseline = resizeImage(baseline, scale); + + // Ensure both images have the same dimensions + int width = scaledBaseline.width; + int height = scaledBaseline.height; + + // Create canvases with background color + PImage actualCanvas = createCanvasWithBackground(scaledActual, width, height); + PImage baselineCanvas = createCanvasWithBackground(scaledBaseline, width, height); + + // Create diff output canvas + PImage diffCanvas = p.createImage(width, height, PImage.RGB); + + // Run pixelmatch equivalent + int diffCount = pixelmatch(actualCanvas, baselineCanvas, diffCanvas, width, height, DEFAULT_THRESHOLD); + + // If no differences, return early + if (diffCount == 0) { + return new ComparisonResult(true, diffCanvas, null); + } + + // Post-process to identify and filter out isolated differences + Set visited = new HashSet<>(); + List clusterSizes = new ArrayList<>(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pos = y * width + x; + + // If this is a diff pixel and not yet visited + if (isDiffPixel(diffCanvas, x, y) && !visited.contains(pos)) { + ClusterInfo clusterInfo = findClusterSize(diffCanvas, x, y, width, height, visited); + clusterSizes.add(clusterInfo); + } + } + } + + // Determine if the differences are significant + List nonLineShiftClusters = clusterSizes.stream() + .filter(cluster -> !cluster.isLineShift && cluster.size >= MIN_CLUSTER_SIZE) + .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + + // Calculate significant differences excluding line shifts + int significantDiffPixels = nonLineShiftClusters.stream() + .mapToInt(cluster -> cluster.size) + .sum(); + + // Determine test result + boolean passed = diffCount == 0 || + significantDiffPixels == 0 || + (significantDiffPixels <= MAX_TOTAL_DIFF_PIXELS && nonLineShiftClusters.size() <= 2); + + ComparisonDetails details = new ComparisonDetails(diffCount, significantDiffPixels, clusterSizes); + + return new ComparisonResult(passed, diffCanvas, details); + } + + private PImage resizeImage(PImage image, double scale) { + int newWidth = (int) Math.ceil(image.width * scale); + int newHeight = (int) Math.ceil(image.height * scale); + + PImage resized = p.createImage(newWidth, newHeight, PImage.RGB); + resized.copy(image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight); + + return resized; + } + + private PImage createCanvasWithBackground(PImage image, int width, int height) { + PImage canvas = p.createImage(width, height, PImage.RGB); + + // Fill with background color (white) + canvas.loadPixels(); + for (int i = 0; i < canvas.pixels.length; i++) { + canvas.pixels[i] = BG_COLOR; + } + canvas.updatePixels(); + + // Draw the image on top + canvas.copy(image, 0, 0, image.width, image.height, 0, 0, image.width, image.height); + + return canvas; + } + + private int pixelmatch(PImage actual, PImage expected, PImage diff, int width, int height, double threshold) { + int diffCount = 0; + + actual.loadPixels(); + expected.loadPixels(); + diff.loadPixels(); + + for (int i = 0; i < actual.pixels.length; i++) { + int actualColor = actual.pixels[i]; + int expectedColor = expected.pixels[i]; + + double delta = colorDelta(actualColor, expectedColor); + + if (delta > threshold) { + // Mark as different (bright red pixel) + diff.pixels[i] = 0xFFFF0000; // Red + diffCount++; + } else { + // Mark as same (dimmed version of actual image) + int dimColor = dimColor(actualColor, ALPHA); + diff.pixels[i] = dimColor; + } + } + + diff.updatePixels(); + return diffCount; + } + + private double colorDelta(int color1, int color2) { + int r1 = (color1 >> 16) & 0xFF; + int g1 = (color1 >> 8) & 0xFF; + int b1 = color1 & 0xFF; + int a1 = (color1 >> 24) & 0xFF; + + int r2 = (color2 >> 16) & 0xFF; + int g2 = (color2 >> 8) & 0xFF; + int b2 = color2 & 0xFF; + int a2 = (color2 >> 24) & 0xFF; + + int dr = r1 - r2; + int dg = g1 - g2; + int db = b1 - b2; + int da = a1 - a2; + + return Math.sqrt(dr * dr + dg * dg + db * db + da * da) / 255.0; + } + + private int dimColor(int color, double alpha) { + int r = (int) (((color >> 16) & 0xFF) * alpha); + int g = (int) (((color >> 8) & 0xFF) * alpha); + int b = (int) ((color & 0xFF) * alpha); + int a = (int) (255 * alpha); + + r = Math.max(0, Math.min(255, r)); + g = Math.max(0, Math.min(255, g)); + b = Math.max(0, Math.min(255, b)); + a = Math.max(0, Math.min(255, a)); + + return (a << 24) | (r << 16) | (g << 8) | b; + } + + private boolean isDiffPixel(PImage image, int x, int y) { + if (x < 0 || x >= image.width || y < 0 || y >= image.height) return false; + + image.loadPixels(); + int color = image.pixels[y * image.width + x]; + + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + + return r == 255 && g == 0 && b == 0; + } + + private ClusterInfo findClusterSize(PImage diffImage, int startX, int startY, int width, int height, Set visited) { + List queue = new ArrayList<>(); + queue.add(new Point2D(startX, startY)); + + int size = 0; + List clusterPixels = new ArrayList<>(); + + while (!queue.isEmpty()) { + Point2D point = queue.remove(0); + int pos = point.y * width + point.x; + + // Skip if already visited + if (visited.contains(pos)) continue; + + // Skip if not a diff pixel + if (!isDiffPixel(diffImage, point.x, point.y)) continue; + + // Mark as visited + visited.add(pos); + size++; + clusterPixels.add(point); + + // Add neighbors to queue + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + + int nx = point.x + dx; + int ny = point.y + dy; + + // Skip if out of bounds + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + + // Skip if already visited + int npos = ny * width + nx; + if (!visited.contains(npos)) { + queue.add(new Point2D(nx, ny)); + } + } + } + } + + // Determine if this is a line shift + boolean isLineShift = detectLineShift(clusterPixels, diffImage, width, height); + + return new ClusterInfo(size, clusterPixels, isLineShift); + } + + private boolean detectLineShift(List clusterPixels, PImage diffImage, int width, int height) { + if (clusterPixels.isEmpty()) return false; + + int linelikePixels = 0; + + for (Point2D pixel : clusterPixels) { + int neighbors = 0; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; // Skip self + + int nx = pixel.x + dx; + int ny = pixel.y + dy; + + // Skip if out of bounds + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + + // Check if neighbor is a diff pixel + if (isDiffPixel(diffImage, nx, ny)) { + neighbors++; + } + } + } + + // Line-like pixels typically have 1-2 neighbors + if (neighbors <= 2) { + linelikePixels++; + } + } + + // If most pixels (>80%) in the cluster have ≤2 neighbors, it's likely a line shift + return (double) linelikePixels / clusterPixels.size() > 0.8; + } + + // Configuration methods + public ImageComparator setMaxSide(int maxSide) { + // For future configurability + return this; + } + + public ImageComparator setMinClusterSize(int minClusterSize) { + // For future configurability + return this; + } + + public ImageComparator setMaxTotalDiffPixels(int maxTotalDiffPixels) { + // For future configurability + return this; + } +} + +// Utility class for algorithm configuration +class ComparatorConfig { + public int maxSide = 400; + public int minClusterSize = 4; + public int maxTotalDiffPixels = 40; + public double threshold = 0.5; + public double alpha = 0.1; + public int backgroundColor = 0xFFFFFFFF; + + public ComparatorConfig() {} + + public ComparatorConfig(int maxSide, int minClusterSize, int maxTotalDiffPixels) { + this.maxSide = maxSide; + this.minClusterSize = minClusterSize; + this.maxTotalDiffPixels = maxTotalDiffPixels; + } +} \ No newline at end of file From a5ded6ae6896d9d9a78952fee98493d35d395798 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 16 Sep 2025 11:17:55 +0530 Subject: [PATCH 03/29] added build.gradle file --- visual-tests/build.gradle.kts | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 visual-tests/build.gradle.kts diff --git a/visual-tests/build.gradle.kts b/visual-tests/build.gradle.kts new file mode 100644 index 0000000000..1aa74d91b7 --- /dev/null +++ b/visual-tests/build.gradle.kts @@ -0,0 +1,67 @@ +plugins { + java + application +} + +repositories { + mavenCentral() + maven { url = uri("https://jogamp.org/deployment/maven") } +} + +//dependencies { +// // Reference to Processing core +// implementation(project(":core")) +// testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") +//} +dependencies { + implementation(project(":core")) + testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") +} + +application { + mainClass.set("ProcessingVisualTestExamples") +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} + +// Visual testing tasks +tasks.register("runVisualTests") { + description = "Run all visual tests" + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("ProcessingVisualTestExamples") +} + +tasks.register("runSimpleTest") { + description = "Verify visual testing setup" + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("SimpleTest") +} + +tasks.register("updateBaselines") { + description = "Update visual test baselines" + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("ProcessingCIHelper") + args("--update") +} + +tasks.register("runCITests") { + description = "Run visual tests in CI" + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("ProcessingCIHelper") + args("--ci") + systemProperty("java.awt.headless", "true") +} + +tasks.register("cleanVisualTestFiles") { + delete(fileTree(".") { + include("__screenshots__/**") + include("diff_*.png") + }) +} + +tasks.named("clean") { + dependsOn("cleanVisualTestFiles") +} \ No newline at end of file From 8e7f7d0fa274b1ec5510bcc377b083da684e2946 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 16 Sep 2025 11:18:35 +0530 Subject: [PATCH 04/29] added the test runner --- .../src/main/java/VisualTestRunner.java | 634 ++++++++++++++++++ 1 file changed, 634 insertions(+) create mode 100644 visual-tests/src/main/java/VisualTestRunner.java diff --git a/visual-tests/src/main/java/VisualTestRunner.java b/visual-tests/src/main/java/VisualTestRunner.java new file mode 100644 index 0000000000..20385ac80c --- /dev/null +++ b/visual-tests/src/main/java/VisualTestRunner.java @@ -0,0 +1,634 @@ +// Processing Visual Test Runner - Modular test execution infrastructure +// Uses ImageComparator for sophisticated pixel matching + +import processing.core.*; +import java.io.*; +import java.nio.file.*; +import java.util.*; + +// Core visual tester class +public class VisualTestRunner { + + private String screenshotDir; + private PixelMatchingAlgorithm pixelMatcher; + private String platform; + + public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher) { + this.pixelMatcher = pixelMatcher; + this.screenshotDir = "__screenshots__"; + this.platform = detectPlatform(); + createDirectoryIfNotExists(screenshotDir); + } + + public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher, String screenshotDir) { + this.pixelMatcher = pixelMatcher; + this.screenshotDir = screenshotDir; + this.platform = detectPlatform(); + createDirectoryIfNotExists(screenshotDir); + } + + // Main test execution method + public TestResult runVisualTest(String testName, ProcessingSketch sketch) { + return runVisualTest(testName, sketch, new TestConfig()); + } + + public TestResult runVisualTest(String testName, ProcessingSketch sketch, TestConfig config) { + try { + System.out.println("Running visual test: " + testName); + + // Capture screenshot from sketch + PImage actualImage = captureSketch(sketch, config); + + // Compare with baseline + ComparisonResult comparison = compareWithBaseline(testName, actualImage, config); + + return new TestResult(testName, comparison); + + } catch (Exception e) { + return TestResult.createError(testName, e.getMessage()); + } + } + + // Capture PImage from Processing sketch + private PImage captureSketch(ProcessingSketch sketch, TestConfig config) { + SketchRunner runner = new SketchRunner(sketch, config); + runner.run(); + return runner.getImage(); + } + + // Compare actual image with baseline + private ComparisonResult compareWithBaseline(String testName, PImage actualImage, TestConfig config) { + String baselinePath = getBaselinePath(testName); + + PImage baselineImage = loadBaseline(baselinePath); + + if (baselineImage == null) { + // First run - save as baseline + saveBaseline(testName, actualImage); + return ComparisonResult.createFirstRun(); + } + + // Use your sophisticated pixel matching algorithm + ComparisonResult result = pixelMatcher.compare(baselineImage, actualImage, config.threshold); + + // Save diff images if test failed + if (!result.passed && result.diffImage != null) { + saveDiffImage(testName, result.diffImage); + } + + return result; + } + + // Save diff image for debugging + private void saveDiffImage(String testName, PImage diffImage) { + String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_]", "-"); + String diffPath = "diff_" + sanitizedName + "-" + platform + ".png"; + diffImage.save(diffPath); + System.out.println("Diff image saved: " + diffPath); + } + + // Utility methods + private String detectPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) return "darwin"; + if (os.contains("win")) return "win32"; + return "linux"; + } + + private void createDirectoryIfNotExists(String dir) { + try { + Files.createDirectories(Paths.get(dir)); + } catch (IOException e) { + System.err.println("Failed to create directory: " + dir); + } + } + + private String getBaselinePath(String testName) { + String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_]", "-"); + return screenshotDir + "/" + sanitizedName + "-" + platform + ".png"; + } + + private PImage loadBaseline(String path) { + File file = new File(path); + if (!file.exists()) return null; + + // Create a temporary PApplet to load the image + PApplet tempApplet = new PApplet(); + return tempApplet.loadImage(path); + } + + private void saveBaseline(String testName, PImage image) { + String path = getBaselinePath(testName); + image.save(path); + System.out.println("Baseline created: " + path); + } +} + +// Test runner that executes Processing sketches +class SketchRunner extends PApplet { + + private ProcessingSketch userSketch; + private TestConfig config; + private PImage capturedImage; + private boolean rendered = false; + + public SketchRunner(ProcessingSketch userSketch, TestConfig config) { + this.userSketch = userSketch; + this.config = config; + } + + public void settings() { + size(config.width, config.height); + } + + public void setup() { + // Disable animations for consistent testing + noLoop(); + + // Set background if specified + if (config.backgroundColor != null) { + background(config.backgroundColor[0], config.backgroundColor[1], config.backgroundColor[2]); + } + + // Call user setup + userSketch.setup(this); + } + + public void draw() { + if (!rendered) { + // Call user draw function + userSketch.draw(this); + + // Capture the frame + capturedImage = get(); // get() returns a PImage of the entire canvas + rendered = true; + } + } + + public void run() { + String[] args = {"SketchRunner"}; + PApplet.runSketch(args, this); + + // Wait for rendering to complete + while (!rendered) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + // Additional wait time for any processing + try { + Thread.sleep(config.renderWaitTime); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // Clean up + exit(); + } + + public PImage getImage() { + return capturedImage; + } +} + +// Interface for user sketches +interface ProcessingSketch { + void setup(PApplet p); + void draw(PApplet p); +} + +// Test configuration class +class TestConfig { + public int width = 800; + public int height = 600; + public int[] backgroundColor = {255, 255, 255}; // RGB + public long renderWaitTime = 2000; // milliseconds + public double threshold = 0.1; + + public TestConfig() {} + + public TestConfig(int width, int height) { + this.width = width; + this.height = height; + } + + public TestConfig(int width, int height, int[] backgroundColor) { + this.width = width; + this.height = height; + this.backgroundColor = backgroundColor; + } + + public TestConfig setThreshold(double threshold) { + this.threshold = threshold; + return this; + } + + public TestConfig setRenderWaitTime(long waitTime) { + this.renderWaitTime = waitTime; + return this; + } +} + +// Enhanced test result with detailed information +class TestResult { + public String testName; + public boolean passed; + public double mismatchRatio; + public String error; + public boolean isFirstRun; + public ComparisonDetails details; + + public TestResult(String testName, ComparisonResult comparison) { + this.testName = testName; + this.passed = comparison.passed; + this.mismatchRatio = comparison.mismatchRatio; + this.isFirstRun = comparison.isFirstRun; + this.details = comparison.details; + } + + public static TestResult createError(String testName, String error) { + TestResult result = new TestResult(); + result.testName = testName; + result.passed = false; + result.error = error; + return result; + } + + private TestResult() {} // For error constructor + + public void printResult() { + System.out.print(testName + ": "); + if (error != null) { + System.out.println("ERROR - " + error); + } else if (isFirstRun) { + System.out.println("BASELINE CREATED"); + } else if (passed) { + System.out.println("PASSED"); + } else { + System.out.println("FAILED (mismatch: " + String.format("%.4f", mismatchRatio * 100) + "%)"); + if (details != null) { + details.printDetails(); + } + } + } +} + +// Test suite for organizing multiple tests +class ProcessingTestSuite { + private VisualTestRunner tester; + private List tests; + + public ProcessingTestSuite(VisualTestRunner tester) { + this.tester = tester; + this.tests = new ArrayList<>(); + } + + public void addTest(String name, ProcessingSketch sketch) { + addTest(name, sketch, new TestConfig()); + } + + public void addTest(String name, ProcessingSketch sketch, TestConfig config) { + tests.add(new VisualTest(name, sketch, config)); + } + + public List runAll() { + System.out.println("Running " + tests.size() + " visual tests..."); + List results = new ArrayList<>(); + + for (VisualTest test : tests) { + TestResult result = tester.runVisualTest(test.name, test.sketch, test.config); + result.printResult(); + results.add(result); + } + + return results; + } + + public TestResult runTest(String testName) { + VisualTest test = tests.stream() + .filter(t -> t.name.equals(testName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("Test not found: " + testName)); + + return tester.runVisualTest(test.name, test.sketch, test.config); + } + + public List getTestNames() { + return tests.stream().map(t -> t.name).collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + } + + // Helper class for internal test storage + private static class VisualTest { + String name; + ProcessingSketch sketch; + TestConfig config; + + VisualTest(String name, ProcessingSketch sketch, TestConfig config) { + this.name = name; + this.sketch = sketch; + this.config = config; + } + } +} + +// Baseline manager for updating reference images +class BaselineManager { + private VisualTestRunner tester; + + public BaselineManager(VisualTestRunner tester) { + this.tester = tester; + } + + public void updateBaseline(String testName, ProcessingSketch sketch) { + updateBaseline(testName, sketch, new TestConfig()); + } + + public void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + System.out.println("Updating baseline for: " + testName); + + // Capture new image + SketchRunner runner = new SketchRunner(sketch, config); + runner.run(); + PImage newImage = runner.getImage(); + + // Save as baseline + String baselinePath = "__screenshots__/" + + testName.replaceAll("[^a-zA-Z0-9-_]", "-") + + "-" + detectPlatform() + ".png"; + newImage.save(baselinePath); + + System.out.println("Baseline updated: " + baselinePath); + } + + public void updateAllBaselines(ProcessingTestSuite suite) { + System.out.println("Updating all baselines..."); + List testNames = suite.getTestNames(); + + for (String testName : testNames) { + // Re-run the test to get the sketch and config + TestResult result = suite.runTest(testName); + // Note: In a real implementation, you'd need to store the sketch reference + // This is a simplified version + } + } + + private String detectPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) return "darwin"; + if (os.contains("win")) return "win32"; + return "linux"; + } +} + +// Test execution utilities +class TestExecutor { + + public static void runSingleTest(String testName, ProcessingSketch sketch) { + runSingleTest(testName, sketch, new TestConfig()); + } + + public static void runSingleTest(String testName, ProcessingSketch sketch, TestConfig config) { + // Initialize comparator + PApplet tempApplet = new PApplet(); + ImageComparator comparator = new ImageComparator(tempApplet); + + // Run test + VisualTestRunner tester = new VisualTestRunner(comparator); + TestResult result = tester.runVisualTest(testName, sketch, config); + result.printResult(); + } + + public static void updateSingleBaseline(String testName, ProcessingSketch sketch) { + updateSingleBaseline(testName, sketch, new TestConfig()); + } + + public static void updateSingleBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + // Initialize comparator + PApplet tempApplet = new PApplet(); + ImageComparator comparator = new ImageComparator(tempApplet); + + // Update baseline + VisualTestRunner tester = new VisualTestRunner(comparator); + BaselineManager manager = new BaselineManager(tester); + manager.updateBaseline(testName, sketch, config); + } +} + +// Example usage and test implementations +class ProcessingVisualTestExamples { + + public static void main(String[] args) { + // Initialize with your sophisticated pixel matching algorithm + PApplet tempApplet = new PApplet(); + + ImageComparator comparator = new ImageComparator(tempApplet); + VisualTestRunner tester = new VisualTestRunner(comparator); + ProcessingTestSuite suite = new ProcessingTestSuite(tester); + + // Add example tests + suite.addTest("red-circle", new RedCircleSketch()); + suite.addTest("blue-square", new BlueSquareSketch()); + suite.addTest("gradient-background", new GradientBackgroundSketch(), + new TestConfig(600, 400)); + suite.addTest("complex-pattern", new ComplexPatternSketch(), + new TestConfig(800, 600).setThreshold(0.15)); + + // Run all tests + List results = suite.runAll(); + + // Print detailed summary + printTestSummary(results); + + // Handle command line arguments + if (args.length > 0) { + handleCommandLineArgs(args, suite); + } + } + + private static void printTestSummary(List results) { + long passed = results.stream().filter(r -> r.passed).count(); + long failed = results.size() - passed; + long baselines = results.stream().filter(r -> r.isFirstRun).count(); + long errors = results.stream().filter(r -> r.error != null).count(); + + System.out.println("\n=== Test Summary ==="); + System.out.println("Total: " + results.size()); + System.out.println("Passed: " + passed); + System.out.println("Failed: " + failed); + System.out.println("Baselines Created: " + baselines); + System.out.println("Errors: " + errors); + + // Print detailed failure information + results.stream() + .filter(r -> !r.passed && !r.isFirstRun && r.error == null) + .forEach(r -> { + System.out.println("\n--- Failed Test: " + r.testName + " ---"); + if (r.details != null) { + r.details.printDetails(); + } + }); + } + + private static void handleCommandLineArgs(String[] args, ProcessingTestSuite suite) { + if (args[0].equals("--update")) { + // Update specific baselines or all + BaselineManager manager = new BaselineManager(null); // Will need tester reference + if (args.length > 1) { + for (int i = 1; i < args.length; i++) { + System.out.println("Updating baseline: " + args[i]); + // Update specific test baseline + } + } else { + System.out.println("Updating all baselines..."); + manager.updateAllBaselines(suite); + } + } else if (args[0].equals("--run")) { + // Run specific test + if (args.length > 1) { + String testName = args[1]; + TestResult result = suite.runTest(testName); + result.printResult(); + } + } + } + + // Example sketch: Red circle + static class RedCircleSketch implements ProcessingSketch { + public void setup(PApplet p) { + p.noStroke(); + } + + public void draw(PApplet p) { + p.background(255); + p.fill(255, 0, 0); + p.ellipse(p.width/2, p.height/2, 100, 100); + } + } + + // Example sketch: Blue square + static class BlueSquareSketch implements ProcessingSketch { + public void setup(PApplet p) { + p.stroke(0); + p.strokeWeight(2); + } + + public void draw(PApplet p) { + p.background(255); + p.fill(0, 0, 255); + p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); + } + } + + // Example sketch: Gradient background + static class GradientBackgroundSketch implements ProcessingSketch { + public void setup(PApplet p) { + p.noStroke(); + } + + public void draw(PApplet p) { + for (int y = 0; y < p.height; y++) { + float inter = PApplet.map(y, 0, p.height, 0, 1); + int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); + p.stroke(c); + p.line(0, y, p.width, y); + } + } + } + + // Example sketch: Complex pattern (more likely to have minor differences) + static class ComplexPatternSketch implements ProcessingSketch { + public void setup(PApplet p) { + p.noStroke(); + } + + public void draw(PApplet p) { + p.background(20, 20, 40); + + for (int x = 0; x < p.width; x += 15) { + for (int y = 0; y < p.height; y += 15) { + float noise = (float) (Math.sin(x * 0.02) * Math.cos(y * 0.02)); + int brightness = (int) ((noise + 1) * 127.5); + + p.fill(brightness, brightness * 0.7f, brightness * 1.2f); + p.rect(x, y, 12, 12); + + // Add some text that might cause line shifts + if (x % 60 == 0 && y % 60 == 0) { + p.fill(255); + p.textSize(8); + p.text(brightness, x + 2, y + 10); + } + } + } + } + } +} + +// CI Integration helper +class ProcessingCIHelper { + + public static void main(String[] args) { + if (args.length > 0 && args[0].equals("--ci")) { + runCITests(); + } else { + ProcessingVisualTestExamples.main(args); + } + } + + public static void runCITests() { + System.out.println("Running visual tests in CI mode..."); + + // Initialize comparator + PApplet tempApplet = new PApplet(); + + ImageComparator comparator = new ImageComparator(tempApplet); + VisualTestRunner tester = new VisualTestRunner(comparator); + ProcessingTestSuite suite = new ProcessingTestSuite(tester); + + // Add your actual test cases here + suite.addTest("ci-test-1", new ProcessingVisualTestExamples.RedCircleSketch()); + suite.addTest("ci-test-2", new ProcessingVisualTestExamples.BlueSquareSketch()); + + // Run tests + List results = suite.runAll(); + + // Check for failures + boolean hasFailures = results.stream().anyMatch(r -> !r.passed && !r.isFirstRun); + boolean hasErrors = results.stream().anyMatch(r -> r.error != null); + + if (hasFailures || hasErrors) { + System.err.println("Visual tests failed!"); + System.exit(1); + } else { + System.out.println("All visual tests passed!"); + System.exit(0); + } + } + +// public static void updateCIBaselines(String[] testNames) { +// System.out.println("Updating baselines in CI mode..."); +// +// // Initialize components +// PApplet tempApplet = new PApplet(); +// +// ImageComparator comparator = new ImageComparator(tempApplet); +// VisualTestRunner = new VisualTestRunner(comparator); +// BaselineManager manager = new BaselineManager(tester); +// +// if (testNames.length == 0) { +// System.out.println("No specific tests specified, updating all..."); +// // Update all baselines - you'd need to implement this based on your test discovery +// } else { +// for (String testName : testNames) { +// System.out.println("Updating baseline for: " + testName); +// // Update specific baseline - you'd need the corresponding sketch +// } +// } +// +// System.out.println("Baseline update completed!"); +// } +} \ No newline at end of file From 2f18f215c988269a4d0a0deb011bc3734079833a Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 16 Sep 2025 11:19:17 +0530 Subject: [PATCH 05/29] added the simple test --- visual-tests/src/main/java/SimpleTest.java | 145 +++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 visual-tests/src/main/java/SimpleTest.java diff --git a/visual-tests/src/main/java/SimpleTest.java b/visual-tests/src/main/java/SimpleTest.java new file mode 100644 index 0000000000..5c59885d4c --- /dev/null +++ b/visual-tests/src/main/java/SimpleTest.java @@ -0,0 +1,145 @@ +// SimpleTest.java - Fixed version for quick verification +import processing.core.*; +import java.util.*; + +public class SimpleTest { + + public static void main(String[] args) { + System.out.println("=== Processing Visual Testing - Quick Test ===\n"); + + try { + // Step 1: Initialize Processing environment + System.out.println("1. Initializing Processing environment..."); + PApplet tempApplet = new PApplet(); + // Fixed: Removed tempApplet.init() - not needed in modern Processing + System.out.println("✓ Processing initialized"); + + // Step 2: Create comparator + System.out.println("2. Creating image comparator..."); + ImageComparator comparator = new ImageComparator(tempApplet); + VisualTestRunner tester = new VisualTestRunner(comparator); + System.out.println("✓ Comparator created"); + + // Step 3: Run basic tests + System.out.println("3. Running basic tests...\n"); + + // Test 1: Simple red circle + System.out.println("--- Test 1: Red Circle ---"); + ProcessingSketch redCircle = new SimpleRedCircle(); + TestResult result1 = tester.runVisualTest("red-circle", redCircle); + result1.printResult(); + + // Test 2: Blue square + System.out.println("\n--- Test 2: Blue Square ---"); + ProcessingSketch blueSquare = new SimpleBlueSquare(); + TestResult result2 = tester.runVisualTest("blue-square", blueSquare); + result2.printResult(); + + // Step 4: Test comparison with identical image (should pass on second run) + if (!result1.isFirstRun) { + System.out.println("\n--- Test 3: Identical Image (Should Pass) ---"); + TestResult result3 = tester.runVisualTest("red-circle", redCircle); + result3.printResult(); + + if (result3.passed) { + System.out.println("✓ Identical image comparison works!"); + } else { + System.out.println("✗ Identical image comparison failed!"); + } + } + + // Step 5: Test comparison with different image (should fail) + System.out.println("\n--- Test 4: Different Image (Should Fail) ---"); + ProcessingSketch greenCircle = new SimpleGreenCircle(); + TestResult result4 = tester.runVisualTest("red-circle", greenCircle); + result4.printResult(); + + if (!result4.passed && !result4.isFirstRun) { + System.out.println("✓ Different image detection works!"); + if (result4.details != null) { + System.out.println("Algorithm detected " + result4.details.totalDiffPixels + " different pixels"); + } + } + + // Step 6: Test suite functionality + System.out.println("\n--- Test 5: Test Suite ---"); + ProcessingTestSuite suite = new ProcessingTestSuite(tester); + suite.addTest("suite-red", new SimpleRedCircle()); + suite.addTest("suite-blue", new SimpleBlueSquare()); + suite.addTest("suite-gradient", new SimpleGradient()); + + List suiteResults = suite.runAll(); + + long passed = suiteResults.stream().filter(r -> r.passed).count(); + long total = suiteResults.size(); + System.out.println("Suite results: " + passed + "/" + total + " passed"); + + // Final summary + System.out.println("\n=== Test Summary ==="); + System.out.println("✓ Processing environment works"); + System.out.println("✓ Image comparator works"); + System.out.println("✓ Baseline creation works"); + System.out.println("✓ Visual test execution works"); + System.out.println("✓ Test suite functionality works"); + System.out.println("\nCheck the '__screenshots__' folder for baseline images!"); + System.out.println("Check for 'diff_*.png' files if any tests failed!"); + + } catch (Exception e) { + System.err.println("Test failed with error: " + e.getMessage()); + e.printStackTrace(); + } + } + + // Simple test sketches + static class SimpleRedCircle implements ProcessingSketch { + public void setup(PApplet p) { + p.noStroke(); + } + + public void draw(PApplet p) { + p.background(255, 255, 255); + p.fill(255, 0, 0); + p.ellipse(p.width/2, p.height/2, 100, 100); + } + } + + static class SimpleBlueSquare implements ProcessingSketch { + public void setup(PApplet p) { + p.stroke(0); + p.strokeWeight(2); + } + + public void draw(PApplet p) { + p.background(255); + p.fill(0, 0, 255); + p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); + } + } + + static class SimpleGreenCircle implements ProcessingSketch { + public void setup(PApplet p) { + p.noStroke(); + } + + public void draw(PApplet p) { + p.background(255, 255, 255); + p.fill(0, 255, 0); // Green instead of red + p.ellipse(p.width/2, p.height/2, 100, 100); + } + } + + static class SimpleGradient implements ProcessingSketch { + public void setup(PApplet p) { + p.noStroke(); + } + + public void draw(PApplet p) { + for (int y = 0; y < p.height; y++) { + float inter = PApplet.map(y, 0, p.height, 0, 1); + int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); + p.stroke(c); + p.line(0, y, p.width, y); + } + } + } +} \ No newline at end of file From c51a8e4b87e07685db23349b0f1a6f255315bfa5 Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Wed, 24 Sep 2025 04:45:10 +0200 Subject: [PATCH 06/29] Revise README for Jetpack Compose migration strategy Updated README to reflect migration to Jetpack Compose and strategy for replacing JEditTextArea with RSyntaxTextArea. Added insights on LSP-based editor research and the need for user feedback on Tweak Mode and autocompletion features. --- app/src/processing/app/syntax/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/processing/app/syntax/README.md b/app/src/processing/app/syntax/README.md index c9af085996..aabe0c2e24 100644 --- a/app/src/processing/app/syntax/README.md +++ b/app/src/processing/app/syntax/README.md @@ -1,10 +1,12 @@ # Replacing our custom version of JEditTextArea -As we have started the migration to Jetpack Compose we will eventually need to replace the JEditTextArea as well. +Since 2025 we have started a migration of Swing to Jetpack Compose and we will eventually need to replace the JEditTextArea as well. -I think a good current strategy would be to start using `RSyntaxTextArea` for the upcoming p5.js mode as it is a better maintained well rounded library. As noted below, a lot of the current state management of the PDE is interetwined with the JEditTextArea implementation. This will force us to decouple the state management out of the `JEditTextArea` whilst also trying to keep backwards compatibility alive for Tweak Mode and the current implementation of autocomplete. +I think a good current strategy would be to start using `RSyntaxTextArea` for an upcoming p5.js mode. `RSyntaxTextArea` is a better maintained and well rounded library. As noted below, a lot of the current state management of the PDE is interetwined with the JEditTextArea implementation. This will force us to decouple the state management out of the `JEditTextArea` whilst also trying to keep backwards compatibility alive for Tweak Mode and the current implementation of autocomplete. -I also did some more research into the potential of using a JS + LSP based editor within the Jetpack Compose but as of writing (early 2025) the only way to do so would be to embed chromium into the PDE through something like [Java-CEF]([url](https://github.com/chromiumembedded/java-cef)) and it looks like a PoC for Jetpack Compose Desktop exists [here](https://github.com/JetBrains/compose-multiplatform/blob/9cd413a4ed125bee5b624550fbd40a05061e912a/experimental/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt). Moving the entire PDE into an electron app would be essentially a rewrite which currrently is not the target. +I also did some more research into the potential of using a JS + LSP based editor within Jetpack Compose but as of writing (early 2025) the only way to do so would be to embed chromium into the PDE through something like [Java-CEF]([url](https://github.com/chromiumembedded/java-cef)) and it looks like a PoC for Jetpack Compose Desktop exists [here](https://github.com/JetBrains/compose-multiplatform/blob/9cd413a4ed125bee5b624550fbd40a05061e912a/experimental/cef/src/main/kotlin/org/jetbrains/compose/desktop/browser/BrowserView.kt). Moving the entire PDE into an electron app would be essentially a rewrite which currrently is not the target. + +Considering the current direction of the build-in LSP within Processing, I would say that creating a LSP based editor would be a good strategy going forward. Research needs to be done on how much the Tweak Mode and autocompletion are _actually_ being used. Currently both these features are quite hidden and I suspect that most users actually move on to more advanced use-cases before they even discover such things. I would like to make both of these features much more prominent within the PDE to test if they are a good value add. From 4e7183fe51d8038f9a276f6db5513fb8332f1827 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 02:04:38 +0530 Subject: [PATCH 07/29] fixing the build issues --- build.gradle.kts | 5 + .../test/visual}/ImageComparator.java | 2 + .../test/visual}/VisualTestRunner.java | 313 +++++------------- 3 files changed, 81 insertions(+), 239 deletions(-) rename visual-tests/src/main/java/{ => processing/test/visual}/ImageComparator.java (99%) rename visual-tests/src/main/java/{ => processing/test/visual}/VisualTestRunner.java (59%) diff --git a/build.gradle.kts b/build.gradle.kts index 0675c2db38..18ef17eb3b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,4 +8,9 @@ plugins { // Set the build directory to not /build to prevent accidental deletion through the clean action // Can be deleted after the migration to Gradle is complete +tasks.register("visualTests") { + description = "Run visual regression tests" + dependsOn(":visual-testing:runVisualTests") +} + layout.buildDirectory = file(".build") \ No newline at end of file diff --git a/visual-tests/src/main/java/ImageComparator.java b/visual-tests/src/main/java/processing/test/visual/ImageComparator.java similarity index 99% rename from visual-tests/src/main/java/ImageComparator.java rename to visual-tests/src/main/java/processing/test/visual/ImageComparator.java index db3eeab971..6420c48cd3 100644 --- a/visual-tests/src/main/java/ImageComparator.java +++ b/visual-tests/src/main/java/processing/test/visual/ImageComparator.java @@ -1,3 +1,5 @@ +package + import processing.core.*; import java.util.*; diff --git a/visual-tests/src/main/java/VisualTestRunner.java b/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java similarity index 59% rename from visual-tests/src/main/java/VisualTestRunner.java rename to visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java index 20385ac80c..b75df0a59f 100644 --- a/visual-tests/src/main/java/VisualTestRunner.java +++ b/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java @@ -1,11 +1,11 @@ -// Processing Visual Test Runner - Modular test execution infrastructure -// Uses ImageComparator for sophisticated pixel matching - import processing.core.*; import java.io.*; import java.nio.file.*; import java.util.*; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; + // Core visual tester class public class VisualTestRunner { @@ -108,29 +108,72 @@ private String getBaselinePath(String testName) { return screenshotDir + "/" + sanitizedName + "-" + platform + ".png"; } + // Replace loadBaseline method: private PImage loadBaseline(String path) { File file = new File(path); - if (!file.exists()) return null; + if (!file.exists()) { + System.out.println("loadBaseline: File doesn't exist: " + file.getAbsolutePath()); + return null; + } - // Create a temporary PApplet to load the image - PApplet tempApplet = new PApplet(); - return tempApplet.loadImage(path); + try { + System.out.println("loadBaseline: Loading from " + file.getAbsolutePath()); + + // Use Java ImageIO instead of PApplet + BufferedImage img = ImageIO.read(file); + + if (img == null) { + System.out.println("loadBaseline: ImageIO returned null"); + return null; + } + + // Convert BufferedImage to PImage + PImage pImg = new PImage(img.getWidth(), img.getHeight(), PImage.RGB); + img.getRGB(0, 0, pImg.width, pImg.height, pImg.pixels, 0, pImg.width); + pImg.updatePixels(); + + System.out.println("loadBaseline: ✓ Loaded " + pImg.width + "x" + pImg.height); + return pImg; + + } catch (Exception e) { + System.err.println("loadBaseline: Error loading image: " + e.getMessage()); + e.printStackTrace(); + return null; + } } + // Replace saveBaseline method: private void saveBaseline(String testName, PImage image) { String path = getBaselinePath(testName); - image.save(path); - System.out.println("Baseline created: " + path); + + if (image == null) { + System.out.println("saveBaseline: ✗ Image is null!"); + return; + } + + try { + // Convert PImage to BufferedImage + BufferedImage bImg = new BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB); + image.loadPixels(); + bImg.setRGB(0, 0, image.width, image.height, image.pixels, 0, image.width); + + // Use Java ImageIO to save + File outputFile = new File(path); + outputFile.getParentFile().mkdirs(); // Ensure directory exists + + ImageIO.write(bImg, "PNG", outputFile); + + } catch (Exception e) { + e.printStackTrace(); + } } } - -// Test runner that executes Processing sketches class SketchRunner extends PApplet { private ProcessingSketch userSketch; private TestConfig config; private PImage capturedImage; - private boolean rendered = false; + private volatile boolean rendered = false; public SketchRunner(ProcessingSketch userSketch, TestConfig config) { this.userSketch = userSketch; @@ -142,7 +185,6 @@ public void settings() { } public void setup() { - // Disable animations for consistent testing noLoop(); // Set background if specified @@ -156,11 +198,8 @@ public void setup() { public void draw() { if (!rendered) { - // Call user draw function userSketch.draw(this); - - // Capture the frame - capturedImage = get(); // get() returns a PImage of the entire canvas + capturedImage = get(); rendered = true; } } @@ -169,25 +208,36 @@ public void run() { String[] args = {"SketchRunner"}; PApplet.runSketch(args, this); - // Wait for rendering to complete - while (!rendered) { + // Simple polling with timeout + int maxWait = 100; // 10 seconds max + int waited = 0; + + while (!rendered && waited < maxWait) { try { - Thread.sleep(10); + Thread.sleep(100); + waited++; + } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } - // Additional wait time for any processing + // Additional wait time try { Thread.sleep(config.renderWaitTime); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } - // Clean up - exit(); + if (surface != null) { + surface.setVisible(false); + } + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } public PImage getImage() { @@ -206,7 +256,7 @@ class TestConfig { public int width = 800; public int height = 600; public int[] backgroundColor = {255, 255, 255}; // RGB - public long renderWaitTime = 2000; // milliseconds + public long renderWaitTime = 100; // milliseconds public double threshold = 0.1; public TestConfig() {} @@ -416,219 +466,4 @@ public static void updateSingleBaseline(String testName, ProcessingSketch sketch BaselineManager manager = new BaselineManager(tester); manager.updateBaseline(testName, sketch, config); } -} - -// Example usage and test implementations -class ProcessingVisualTestExamples { - - public static void main(String[] args) { - // Initialize with your sophisticated pixel matching algorithm - PApplet tempApplet = new PApplet(); - - ImageComparator comparator = new ImageComparator(tempApplet); - VisualTestRunner tester = new VisualTestRunner(comparator); - ProcessingTestSuite suite = new ProcessingTestSuite(tester); - - // Add example tests - suite.addTest("red-circle", new RedCircleSketch()); - suite.addTest("blue-square", new BlueSquareSketch()); - suite.addTest("gradient-background", new GradientBackgroundSketch(), - new TestConfig(600, 400)); - suite.addTest("complex-pattern", new ComplexPatternSketch(), - new TestConfig(800, 600).setThreshold(0.15)); - - // Run all tests - List results = suite.runAll(); - - // Print detailed summary - printTestSummary(results); - - // Handle command line arguments - if (args.length > 0) { - handleCommandLineArgs(args, suite); - } - } - - private static void printTestSummary(List results) { - long passed = results.stream().filter(r -> r.passed).count(); - long failed = results.size() - passed; - long baselines = results.stream().filter(r -> r.isFirstRun).count(); - long errors = results.stream().filter(r -> r.error != null).count(); - - System.out.println("\n=== Test Summary ==="); - System.out.println("Total: " + results.size()); - System.out.println("Passed: " + passed); - System.out.println("Failed: " + failed); - System.out.println("Baselines Created: " + baselines); - System.out.println("Errors: " + errors); - - // Print detailed failure information - results.stream() - .filter(r -> !r.passed && !r.isFirstRun && r.error == null) - .forEach(r -> { - System.out.println("\n--- Failed Test: " + r.testName + " ---"); - if (r.details != null) { - r.details.printDetails(); - } - }); - } - - private static void handleCommandLineArgs(String[] args, ProcessingTestSuite suite) { - if (args[0].equals("--update")) { - // Update specific baselines or all - BaselineManager manager = new BaselineManager(null); // Will need tester reference - if (args.length > 1) { - for (int i = 1; i < args.length; i++) { - System.out.println("Updating baseline: " + args[i]); - // Update specific test baseline - } - } else { - System.out.println("Updating all baselines..."); - manager.updateAllBaselines(suite); - } - } else if (args[0].equals("--run")) { - // Run specific test - if (args.length > 1) { - String testName = args[1]; - TestResult result = suite.runTest(testName); - result.printResult(); - } - } - } - - // Example sketch: Red circle - static class RedCircleSketch implements ProcessingSketch { - public void setup(PApplet p) { - p.noStroke(); - } - - public void draw(PApplet p) { - p.background(255); - p.fill(255, 0, 0); - p.ellipse(p.width/2, p.height/2, 100, 100); - } - } - - // Example sketch: Blue square - static class BlueSquareSketch implements ProcessingSketch { - public void setup(PApplet p) { - p.stroke(0); - p.strokeWeight(2); - } - - public void draw(PApplet p) { - p.background(255); - p.fill(0, 0, 255); - p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); - } - } - - // Example sketch: Gradient background - static class GradientBackgroundSketch implements ProcessingSketch { - public void setup(PApplet p) { - p.noStroke(); - } - - public void draw(PApplet p) { - for (int y = 0; y < p.height; y++) { - float inter = PApplet.map(y, 0, p.height, 0, 1); - int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); - p.stroke(c); - p.line(0, y, p.width, y); - } - } - } - - // Example sketch: Complex pattern (more likely to have minor differences) - static class ComplexPatternSketch implements ProcessingSketch { - public void setup(PApplet p) { - p.noStroke(); - } - - public void draw(PApplet p) { - p.background(20, 20, 40); - - for (int x = 0; x < p.width; x += 15) { - for (int y = 0; y < p.height; y += 15) { - float noise = (float) (Math.sin(x * 0.02) * Math.cos(y * 0.02)); - int brightness = (int) ((noise + 1) * 127.5); - - p.fill(brightness, brightness * 0.7f, brightness * 1.2f); - p.rect(x, y, 12, 12); - - // Add some text that might cause line shifts - if (x % 60 == 0 && y % 60 == 0) { - p.fill(255); - p.textSize(8); - p.text(brightness, x + 2, y + 10); - } - } - } - } - } -} - -// CI Integration helper -class ProcessingCIHelper { - - public static void main(String[] args) { - if (args.length > 0 && args[0].equals("--ci")) { - runCITests(); - } else { - ProcessingVisualTestExamples.main(args); - } - } - - public static void runCITests() { - System.out.println("Running visual tests in CI mode..."); - - // Initialize comparator - PApplet tempApplet = new PApplet(); - - ImageComparator comparator = new ImageComparator(tempApplet); - VisualTestRunner tester = new VisualTestRunner(comparator); - ProcessingTestSuite suite = new ProcessingTestSuite(tester); - - // Add your actual test cases here - suite.addTest("ci-test-1", new ProcessingVisualTestExamples.RedCircleSketch()); - suite.addTest("ci-test-2", new ProcessingVisualTestExamples.BlueSquareSketch()); - - // Run tests - List results = suite.runAll(); - - // Check for failures - boolean hasFailures = results.stream().anyMatch(r -> !r.passed && !r.isFirstRun); - boolean hasErrors = results.stream().anyMatch(r -> r.error != null); - - if (hasFailures || hasErrors) { - System.err.println("Visual tests failed!"); - System.exit(1); - } else { - System.out.println("All visual tests passed!"); - System.exit(0); - } - } - -// public static void updateCIBaselines(String[] testNames) { -// System.out.println("Updating baselines in CI mode..."); -// -// // Initialize components -// PApplet tempApplet = new PApplet(); -// -// ImageComparator comparator = new ImageComparator(tempApplet); -// VisualTestRunner = new VisualTestRunner(comparator); -// BaselineManager manager = new BaselineManager(tester); -// -// if (testNames.length == 0) { -// System.out.println("No specific tests specified, updating all..."); -// // Update all baselines - you'd need to implement this based on your test discovery -// } else { -// for (String testName : testNames) { -// System.out.println("Updating baseline for: " + testName); -// // Update specific baseline - you'd need the corresponding sketch -// } -// } -// -// System.out.println("Baseline update completed!"); -// } } \ No newline at end of file From 593413819e641cc057f7a5e5b7be27e25eb638dd Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 02:06:13 +0530 Subject: [PATCH 08/29] added junit as dependency --- visual-tests/build.gradle.kts | 187 ++++++++++++++++++++++++++++++---- 1 file changed, 170 insertions(+), 17 deletions(-) diff --git a/visual-tests/build.gradle.kts b/visual-tests/build.gradle.kts index 1aa74d91b7..648be87a75 100644 --- a/visual-tests/build.gradle.kts +++ b/visual-tests/build.gradle.kts @@ -15,9 +15,17 @@ repositories { //} dependencies { implementation(project(":core")) - testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") + + // JUnit BOM to manage versions + testImplementation(platform("org.junit:junit-bom:5.9.3")) + testImplementation("org.junit.jupiter:junit-jupiter") + //testRuntimeOnly("org.junit.platform:test-platform-launcher:1.9.3") + + // Optional: AssertJ for better assertions + testImplementation("org.assertj:assertj-core:3.24.2") } + application { mainClass.set("ProcessingVisualTestExamples") } @@ -27,39 +35,184 @@ java { targetCompatibility = JavaVersion.VERSION_17 } -// Visual testing tasks -tasks.register("runVisualTests") { - description = "Run all visual tests" - classpath = sourceSets.main.get().runtimeClasspath - mainClass.set("ProcessingVisualTestExamples") +//// Visual testing tasks +//tasks.register("runVisualTests") { +// description = "Run all visual tests" +// classpath = sourceSets.main.get().runtimeClasspath +// mainClass.set("ProcessingVisualTestExamples") +//} +// +//tasks.register("runSimpleTest") { +// description = "Verify visual testing setup" +// classpath = sourceSets.main.get().runtimeClasspath +// mainClass.set("SimpleTest") +//} +// +//tasks.register("updateBaselines") { +// description = "Update visual test baselines" +// classpath = sourceSets.main.get().runtimeClasspath +// mainClass.set("ProcessingCIHelper") +// args("--update") +//} +// +//tasks.register("runCITests") { +// description = "Run visual tests in CI" +// classpath = sourceSets.main.get().runtimeClasspath +// mainClass.set("ProcessingCIHelper") +// args("--ci") +// systemProperty("java.awt.headless", "true") +//} +// +//tasks.register("cleanVisualTestFiles") { +// delete(fileTree(".") { +// include("__screenshots__/**") +// include("diff_*.png") +// }) +//} +// +//tasks.named("clean") { +// dependsOn("cleanVisualTestFiles") +//} + +tasks.test { + useJUnitPlatform() + + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } + + // Disable parallel execution to avoid Processing window conflicts + maxParallelForks = 1 + + // Add system properties + systemProperty("java.awt.headless", "false") +} + +// Task to update baselines using JUnit +tasks.register("updateBaselines") { + description = "Update visual test baselines" + group = "verification" + + useJUnitPlatform { + includeTags("baseline") + } + + systemProperty("update.baselines", "true") + maxParallelForks = 1 + + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = true + } +} + +// Task to run only visual tests (excluding slow tests) +tasks.register("visualTest") { + description = "Run visual tests (excluding slow tests)" + group = "verification" + + useJUnitPlatform { + excludeTags("slow") + } + + maxParallelForks = 1 } +// Task to run tests for specific feature +tasks.register("testShapes") { + description = "Run shape-related visual tests" + group = "verification" + + useJUnitPlatform { + includeTags("shapes") + } + + maxParallelForks = 1 +} + +// Legacy task - keep for backward compatibility during migration tasks.register("runSimpleTest") { - description = "Verify visual testing setup" + description = "[DEPRECATED] Use 'test' instead - Verify visual testing setup" + group = "verification" classpath = sourceSets.main.get().runtimeClasspath mainClass.set("SimpleTest") + + doFirst { + println("⚠️ WARNING: This task is deprecated. Please use './gradlew test' instead") + } } -tasks.register("updateBaselines") { - description = "Update visual test baselines" +// Legacy task - keep for backward compatibility +tasks.register("runVisualTests") { + description = "[DEPRECATED] Use 'test' instead - Run all visual tests" + group = "verification" classpath = sourceSets.main.get().runtimeClasspath - mainClass.set("ProcessingCIHelper") - args("--update") + mainClass.set("ProcessingVisualTestExamples") + + doFirst { + println("⚠️ WARNING: This task is deprecated. Please use './gradlew test' instead") + } } -tasks.register("runCITests") { - description = "Run visual tests in CI" - classpath = sourceSets.main.get().runtimeClasspath - mainClass.set("ProcessingCIHelper") - args("--ci") - systemProperty("java.awt.headless", "true") +// CI-specific test task +tasks.register("ciTest") { + description = "Run visual tests in CI mode" + group = "verification" + + useJUnitPlatform() + + outputs.upToDateWhen { false } + + maxParallelForks = 1 + + testLogging { + events("passed", "skipped", "failed") + showStandardStreams = true + //exceptionFormat = org.gradle.api.tasks.testing.TestExceptionFormat.FULL + } + + // Generate XML reports for CI + reports { + junitXml.required.set(true) + html.required.set(true) + } + + // Fail fast in CI + failFast = true } +// Clean task for visual test artifacts tasks.register("cleanVisualTestFiles") { + description = "Clean visual test artifacts" + group = "build" + + delete(fileTree(".") { + include("diff_*.png") + }) + + // Don't delete baselines by default - be explicit + doLast { + println("✓ Cleaned diff images") + println(" Baselines preserved in __screenshots__/") + println(" To clean baselines: rm -rf __screenshots__/") + } +} + +// Separate task to clean everything including baselines (dangerous!) +tasks.register("cleanAllVisualTestFiles") { + description = "Clean ALL visual test files INCLUDING BASELINES (use with caution!)" + group = "build" + delete(fileTree(".") { include("__screenshots__/**") include("diff_*.png") }) + + doFirst { + println("⚠️ WARNING: This will delete all baseline images!") + } } tasks.named("clean") { From c4915dd5750918f48048009f8e24ce2aaceedd59 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 02:07:41 +0530 Subject: [PATCH 09/29] removing custom class implementation --- visual-tests/src/main/java/SimpleTest.java | 250 +++++++++------------ 1 file changed, 105 insertions(+), 145 deletions(-) diff --git a/visual-tests/src/main/java/SimpleTest.java b/visual-tests/src/main/java/SimpleTest.java index 5c59885d4c..ae31df988c 100644 --- a/visual-tests/src/main/java/SimpleTest.java +++ b/visual-tests/src/main/java/SimpleTest.java @@ -1,145 +1,105 @@ -// SimpleTest.java - Fixed version for quick verification -import processing.core.*; -import java.util.*; - -public class SimpleTest { - - public static void main(String[] args) { - System.out.println("=== Processing Visual Testing - Quick Test ===\n"); - - try { - // Step 1: Initialize Processing environment - System.out.println("1. Initializing Processing environment..."); - PApplet tempApplet = new PApplet(); - // Fixed: Removed tempApplet.init() - not needed in modern Processing - System.out.println("✓ Processing initialized"); - - // Step 2: Create comparator - System.out.println("2. Creating image comparator..."); - ImageComparator comparator = new ImageComparator(tempApplet); - VisualTestRunner tester = new VisualTestRunner(comparator); - System.out.println("✓ Comparator created"); - - // Step 3: Run basic tests - System.out.println("3. Running basic tests...\n"); - - // Test 1: Simple red circle - System.out.println("--- Test 1: Red Circle ---"); - ProcessingSketch redCircle = new SimpleRedCircle(); - TestResult result1 = tester.runVisualTest("red-circle", redCircle); - result1.printResult(); - - // Test 2: Blue square - System.out.println("\n--- Test 2: Blue Square ---"); - ProcessingSketch blueSquare = new SimpleBlueSquare(); - TestResult result2 = tester.runVisualTest("blue-square", blueSquare); - result2.printResult(); - - // Step 4: Test comparison with identical image (should pass on second run) - if (!result1.isFirstRun) { - System.out.println("\n--- Test 3: Identical Image (Should Pass) ---"); - TestResult result3 = tester.runVisualTest("red-circle", redCircle); - result3.printResult(); - - if (result3.passed) { - System.out.println("✓ Identical image comparison works!"); - } else { - System.out.println("✗ Identical image comparison failed!"); - } - } - - // Step 5: Test comparison with different image (should fail) - System.out.println("\n--- Test 4: Different Image (Should Fail) ---"); - ProcessingSketch greenCircle = new SimpleGreenCircle(); - TestResult result4 = tester.runVisualTest("red-circle", greenCircle); - result4.printResult(); - - if (!result4.passed && !result4.isFirstRun) { - System.out.println("✓ Different image detection works!"); - if (result4.details != null) { - System.out.println("Algorithm detected " + result4.details.totalDiffPixels + " different pixels"); - } - } - - // Step 6: Test suite functionality - System.out.println("\n--- Test 5: Test Suite ---"); - ProcessingTestSuite suite = new ProcessingTestSuite(tester); - suite.addTest("suite-red", new SimpleRedCircle()); - suite.addTest("suite-blue", new SimpleBlueSquare()); - suite.addTest("suite-gradient", new SimpleGradient()); - - List suiteResults = suite.runAll(); - - long passed = suiteResults.stream().filter(r -> r.passed).count(); - long total = suiteResults.size(); - System.out.println("Suite results: " + passed + "/" + total + " passed"); - - // Final summary - System.out.println("\n=== Test Summary ==="); - System.out.println("✓ Processing environment works"); - System.out.println("✓ Image comparator works"); - System.out.println("✓ Baseline creation works"); - System.out.println("✓ Visual test execution works"); - System.out.println("✓ Test suite functionality works"); - System.out.println("\nCheck the '__screenshots__' folder for baseline images!"); - System.out.println("Check for 'diff_*.png' files if any tests failed!"); - - } catch (Exception e) { - System.err.println("Test failed with error: " + e.getMessage()); - e.printStackTrace(); - } - } - - // Simple test sketches - static class SimpleRedCircle implements ProcessingSketch { - public void setup(PApplet p) { - p.noStroke(); - } - - public void draw(PApplet p) { - p.background(255, 255, 255); - p.fill(255, 0, 0); - p.ellipse(p.width/2, p.height/2, 100, 100); - } - } - - static class SimpleBlueSquare implements ProcessingSketch { - public void setup(PApplet p) { - p.stroke(0); - p.strokeWeight(2); - } - - public void draw(PApplet p) { - p.background(255); - p.fill(0, 0, 255); - p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); - } - } - - static class SimpleGreenCircle implements ProcessingSketch { - public void setup(PApplet p) { - p.noStroke(); - } - - public void draw(PApplet p) { - p.background(255, 255, 255); - p.fill(0, 255, 0); // Green instead of red - p.ellipse(p.width/2, p.height/2, 100, 100); - } - } - - static class SimpleGradient implements ProcessingSketch { - public void setup(PApplet p) { - p.noStroke(); - } - - public void draw(PApplet p) { - for (int y = 0; y < p.height; y++) { - float inter = PApplet.map(y, 0, p.height, 0, 1); - int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); - p.stroke(c); - p.line(0, y, p.width, y); - } - } - } -} \ No newline at end of file +//// SimpleTest.java - Fixed version for quick verification +//import processing.core.*; +//import java.util.*; +// +//public class SimpleTest { +// +// public static void main(String[] args) { +// System.out.println("=== Processing Visual Testing - Quick Test ===\n"); +// +// try { +// PApplet tempApplet = new PApplet(); +// +// ImageComparator comparator = new ImageComparator(tempApplet); +// VisualTestRunner tester = new VisualTestRunner(comparator); +// +// ProcessingSketch redCircle = new SimpleRedCircle(); +// TestResult result1 = tester.runVisualTest("red-circle", redCircle); +// result1.printResult(); +// +// ProcessingSketch blueSquare = new SimpleBlueSquare(); +// TestResult result2 = tester.runVisualTest("blue-square", blueSquare); +// result2.printResult(); +// +// // Step 4: Test comparison with identical image (should pass on second run) +// if (!result1.isFirstRun) { +// TestResult result3 = tester.runVisualTest("red-circle", redCircle); +// result3.printResult(); +// +// if (result3.passed) { +// System.out.println("✓ Identical image comparison works!"); +// } else { +// System.out.println("✗ Identical image comparison failed!"); +// } +// } +// ProcessingTestSuite suite = new ProcessingTestSuite(tester); +// suite.addTest("suite-red", new SimpleRedCircle()); +// suite.addTest("suite-blue", new SimpleBlueSquare()); +// suite.addTest("suite-gradient", new SimpleGradient()); +// +// List suiteResults = suite.runAll(); +// +// long passed = suiteResults.stream().filter(r -> r.passed).count(); +// long total = suiteResults.size(); +// System.out.println("Suite results: " + passed + "/" + total + " passed"); +// System.exit(0); +// +// } catch (Exception e) { +// System.err.println("Test failed with error: " + e.getMessage()); +// e.printStackTrace(); +// } +// } +// +// // Simple test sketches +// static class SimpleRedCircle implements ProcessingSketch { +// public void setup(PApplet p) { +// p.noStroke(); +// } +// +// public void draw(PApplet p) { +// p.background(255, 255, 255); +// p.fill(255, 0, 0); +// p.ellipse(p.width/2, p.height/2, 100, 100); +// } +// } +// +// static class SimpleBlueSquare implements ProcessingSketch { +// public void setup(PApplet p) { +// p.stroke(0); +// p.strokeWeight(2); +// } +// +// public void draw(PApplet p) { +// p.background(255); +// p.fill(0, 0, 255); +// p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); +// } +// } +// +// static class SimpleGreenCircle implements ProcessingSketch { +// public void setup(PApplet p) { +// p.noStroke(); +// } +// +// public void draw(PApplet p) { +// p.background(255, 255, 255); +// p.fill(0, 255, 0); // Green instead of red +// p.ellipse(p.width/2, p.height/2, 100, 100); +// } +// } +// +// static class SimpleGradient implements ProcessingSketch { +// public void setup(PApplet p) { +// p.noStroke(); +// } +// +// public void draw(PApplet p) { +// for (int y = 0; y < p.height; y++) { +// float inter = PApplet.map(y, 0, p.height, 0, 1); +// int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); +// p.stroke(c); +// p.line(0, y, p.width, y); +// } +// } +// } +//} \ No newline at end of file From d549a1813ac0ecdf78a072f157ecf3cfadbd72ee Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 02:08:20 +0530 Subject: [PATCH 10/29] inclding visual-tests in settings --- settings.gradle.kts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 8f8cb74c7f..b0be4a3763 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,5 +11,7 @@ include( "java:libraries:pdf", "java:libraries:serial", "java:libraries:svg", + ":visual-tests" ) -include("app:utils") \ No newline at end of file +include("app:utils") +include(":visual-tests") \ No newline at end of file From cac895a14a0038dbd41ae94a48b8f9064ee4d555 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 03:32:41 +0530 Subject: [PATCH 11/29] fixed the overlapping cmd --- build.gradle.kts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 18ef17eb3b..525641e505 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,9 +8,5 @@ plugins { // Set the build directory to not /build to prevent accidental deletion through the clean action // Can be deleted after the migration to Gradle is complete -tasks.register("visualTests") { - description = "Run visual regression tests" - dependsOn(":visual-testing:runVisualTests") -} layout.buildDirectory = file(".build") \ No newline at end of file From 803516feed383bfc07f8ac449f2a27f783ab5c27 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 03:34:18 +0530 Subject: [PATCH 12/29] cleaning --- visual-tests/build.gradle.kts | 143 ++++++++++++++++++---------------- 1 file changed, 75 insertions(+), 68 deletions(-) diff --git a/visual-tests/build.gradle.kts b/visual-tests/build.gradle.kts index 648be87a75..f5931f0a21 100644 --- a/visual-tests/build.gradle.kts +++ b/visual-tests/build.gradle.kts @@ -8,20 +8,14 @@ repositories { maven { url = uri("https://jogamp.org/deployment/maven") } } -//dependencies { -// // Reference to Processing core -// implementation(project(":core")) -// testImplementation("org.junit.jupiter:junit-jupiter:5.9.2") -//} dependencies { implementation(project(":core")) // JUnit BOM to manage versions testImplementation(platform("org.junit:junit-bom:5.9.3")) testImplementation("org.junit.jupiter:junit-jupiter") - //testRuntimeOnly("org.junit.platform:test-platform-launcher:1.9.3") + testImplementation("org.junit.platform:junit-platform-suite:1.9.3") - // Optional: AssertJ for better assertions testImplementation("org.assertj:assertj-core:3.24.2") } @@ -35,45 +29,6 @@ java { targetCompatibility = JavaVersion.VERSION_17 } -//// Visual testing tasks -//tasks.register("runVisualTests") { -// description = "Run all visual tests" -// classpath = sourceSets.main.get().runtimeClasspath -// mainClass.set("ProcessingVisualTestExamples") -//} -// -//tasks.register("runSimpleTest") { -// description = "Verify visual testing setup" -// classpath = sourceSets.main.get().runtimeClasspath -// mainClass.set("SimpleTest") -//} -// -//tasks.register("updateBaselines") { -// description = "Update visual test baselines" -// classpath = sourceSets.main.get().runtimeClasspath -// mainClass.set("ProcessingCIHelper") -// args("--update") -//} -// -//tasks.register("runCITests") { -// description = "Run visual tests in CI" -// classpath = sourceSets.main.get().runtimeClasspath -// mainClass.set("ProcessingCIHelper") -// args("--ci") -// systemProperty("java.awt.headless", "true") -//} -// -//tasks.register("cleanVisualTestFiles") { -// delete(fileTree(".") { -// include("__screenshots__/**") -// include("diff_*.png") -// }) -//} -// -//tasks.named("clean") { -// dependsOn("cleanVisualTestFiles") -//} - tasks.test { useJUnitPlatform() @@ -108,52 +63,104 @@ tasks.register("updateBaselines") { } } -// Task to run only visual tests (excluding slow tests) -tasks.register("visualTest") { - description = "Run visual tests (excluding slow tests)" +tasks.register("testShapes") { + description = "Run shape-related visual tests" group = "verification" useJUnitPlatform { - excludeTags("slow") + includeTags("shapes") } maxParallelForks = 1 } -// Task to run tests for specific feature -tasks.register("testShapes") { - description = "Run shape-related visual tests" +tasks.register("testBasicShapes") { + description = "Run basic shapes visual tests" group = "verification" useJUnitPlatform { - includeTags("shapes") + includeTags("basic") } + outputs.upToDateWhen { false } maxParallelForks = 1 + + // Add test logging to see what's happening + testLogging { + events("passed", "skipped", "failed", "started") + showStandardStreams = true + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + } } -// Legacy task - keep for backward compatibility during migration -tasks.register("runSimpleTest") { - description = "[DEPRECATED] Use 'test' instead - Verify visual testing setup" +// Task to run ONLY visual tests (no other test types) +tasks.register("visualTests") { + description = "Run all visual tests" group = "verification" - classpath = sourceSets.main.get().runtimeClasspath - mainClass.set("SimpleTest") - doFirst { - println("⚠️ WARNING: This task is deprecated. Please use './gradlew test' instead") + useJUnitPlatform { + // Include all tests in the visual test package + includeEngines("junit-jupiter") + } + + filter { + includeTestsMatching("visual.*") + } + + outputs.upToDateWhen { false } + maxParallelForks = 1 + + testLogging { + events("passed", "skipped", "failed", "started") + showStandardStreams = true + displayGranularity = 2 } } -// Legacy task - keep for backward compatibility -tasks.register("runVisualTests") { - description = "[DEPRECATED] Use 'test' instead - Run all visual tests" +tasks.register("testRendering") { + description = "Run rendering visual tests" group = "verification" - classpath = sourceSets.main.get().runtimeClasspath - mainClass.set("ProcessingVisualTestExamples") - doFirst { - println("⚠️ WARNING: This task is deprecated. Please use './gradlew test' instead") + useJUnitPlatform { + includeTags("rendering") } + + outputs.upToDateWhen { false } + maxParallelForks = 1 + + testLogging { + events("passed", "skipped", "failed", "started") + showStandardStreams = true + } +} + +tasks.register("runSuite") { + description = "Run specific test suite (use -PsuiteClass=SuiteName)" + group = "verification" + + useJUnitPlatform { + val suiteClass = project.findProperty("suiteClass") as String? + ?: "visual.suites.AllVisualTests" + includeTags(suiteClass) + } + + outputs.upToDateWhen { false } + maxParallelForks = 1 +} + +// Update baselines for specific suite +tasks.register("updateBaselinesForSuite") { + description = "Update baselines for specific suite (use -Psuite=tag)" + group = "verification" + + useJUnitPlatform { + val suite = project.findProperty("suite") as String? ?: "baseline" + includeTags(suite, "baseline") + } + + systemProperty("update.baselines", "true") + outputs.upToDateWhen { false } + maxParallelForks = 1 } // CI-specific test task From ace6f7bec12a18ac3739e5d965d04f1cfa2e44b3 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 03:35:37 +0530 Subject: [PATCH 13/29] adding packages --- .../test/visual/ImageComparator.java | 2 +- .../test/visual/VisualTestRunner.java | 192 ++---------------- 2 files changed, 23 insertions(+), 171 deletions(-) diff --git a/visual-tests/src/main/java/processing/test/visual/ImageComparator.java b/visual-tests/src/main/java/processing/test/visual/ImageComparator.java index 6420c48cd3..66bb34b504 100644 --- a/visual-tests/src/main/java/processing/test/visual/ImageComparator.java +++ b/visual-tests/src/main/java/processing/test/visual/ImageComparator.java @@ -1,4 +1,4 @@ -package +package processing.test.visual; import processing.core.*; import java.util.*; diff --git a/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java b/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java index b75df0a59f..0f0ffe1003 100644 --- a/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java +++ b/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java @@ -1,3 +1,5 @@ +package processing.test.visual; + import processing.core.*; import java.io.*; import java.nio.file.*; @@ -82,7 +84,16 @@ private ComparisonResult compareWithBaseline(String testName, PImage actualImage // Save diff image for debugging private void saveDiffImage(String testName, PImage diffImage) { String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_]", "-"); - String diffPath = "diff_" + sanitizedName + "-" + platform + ".png"; + String diffPath; + if (sanitizedName.contains("/")) { + diffPath = "diff_" + sanitizedName.replace("/", "_") + "-" + platform + ".png"; + } else { + diffPath = "diff_" + sanitizedName + "-" + platform + ".png"; + } + + File diffFile = new File(diffPath); + diffFile.getParentFile().mkdirs(); + diffImage.save(diffPath); System.out.println("Diff image saved: " + diffPath); } @@ -104,8 +115,9 @@ private void createDirectoryIfNotExists(String dir) { } private String getBaselinePath(String testName) { - String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_]", "-"); - return screenshotDir + "/" + sanitizedName + "-" + platform + ".png"; + String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_/]", "-"); + + return screenshotDir + "/" + sanitizedName + "-" + platform + ".png"; } // Replace loadBaseline method: @@ -157,13 +169,17 @@ private void saveBaseline(String testName, PImage image) { image.loadPixels(); bImg.setRGB(0, 0, image.width, image.height, image.pixels, 0, image.width); - // Use Java ImageIO to save + // Create File object and ensure parent directories exist File outputFile = new File(path); - outputFile.getParentFile().mkdirs(); // Ensure directory exists + outputFile.getParentFile().mkdirs(); // This creates nested directories + // Use Java ImageIO to save ImageIO.write(bImg, "PNG", outputFile); + System.out.println("Baseline saved: " + path); + } catch (Exception e) { + System.err.println("Failed to save baseline: " + path); e.printStackTrace(); } } @@ -201,6 +217,7 @@ public void draw() { userSketch.draw(this); capturedImage = get(); rendered = true; + noLoop(); } } @@ -245,88 +262,6 @@ public PImage getImage() { } } -// Interface for user sketches -interface ProcessingSketch { - void setup(PApplet p); - void draw(PApplet p); -} - -// Test configuration class -class TestConfig { - public int width = 800; - public int height = 600; - public int[] backgroundColor = {255, 255, 255}; // RGB - public long renderWaitTime = 100; // milliseconds - public double threshold = 0.1; - - public TestConfig() {} - - public TestConfig(int width, int height) { - this.width = width; - this.height = height; - } - - public TestConfig(int width, int height, int[] backgroundColor) { - this.width = width; - this.height = height; - this.backgroundColor = backgroundColor; - } - - public TestConfig setThreshold(double threshold) { - this.threshold = threshold; - return this; - } - - public TestConfig setRenderWaitTime(long waitTime) { - this.renderWaitTime = waitTime; - return this; - } -} - -// Enhanced test result with detailed information -class TestResult { - public String testName; - public boolean passed; - public double mismatchRatio; - public String error; - public boolean isFirstRun; - public ComparisonDetails details; - - public TestResult(String testName, ComparisonResult comparison) { - this.testName = testName; - this.passed = comparison.passed; - this.mismatchRatio = comparison.mismatchRatio; - this.isFirstRun = comparison.isFirstRun; - this.details = comparison.details; - } - - public static TestResult createError(String testName, String error) { - TestResult result = new TestResult(); - result.testName = testName; - result.passed = false; - result.error = error; - return result; - } - - private TestResult() {} // For error constructor - - public void printResult() { - System.out.print(testName + ": "); - if (error != null) { - System.out.println("ERROR - " + error); - } else if (isFirstRun) { - System.out.println("BASELINE CREATED"); - } else if (passed) { - System.out.println("PASSED"); - } else { - System.out.println("FAILED (mismatch: " + String.format("%.4f", mismatchRatio * 100) + "%)"); - if (details != null) { - details.printDetails(); - } - } - } -} - // Test suite for organizing multiple tests class ProcessingTestSuite { private VisualTestRunner tester; @@ -384,86 +319,3 @@ private static class VisualTest { } } } - -// Baseline manager for updating reference images -class BaselineManager { - private VisualTestRunner tester; - - public BaselineManager(VisualTestRunner tester) { - this.tester = tester; - } - - public void updateBaseline(String testName, ProcessingSketch sketch) { - updateBaseline(testName, sketch, new TestConfig()); - } - - public void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { - System.out.println("Updating baseline for: " + testName); - - // Capture new image - SketchRunner runner = new SketchRunner(sketch, config); - runner.run(); - PImage newImage = runner.getImage(); - - // Save as baseline - String baselinePath = "__screenshots__/" + - testName.replaceAll("[^a-zA-Z0-9-_]", "-") + - "-" + detectPlatform() + ".png"; - newImage.save(baselinePath); - - System.out.println("Baseline updated: " + baselinePath); - } - - public void updateAllBaselines(ProcessingTestSuite suite) { - System.out.println("Updating all baselines..."); - List testNames = suite.getTestNames(); - - for (String testName : testNames) { - // Re-run the test to get the sketch and config - TestResult result = suite.runTest(testName); - // Note: In a real implementation, you'd need to store the sketch reference - // This is a simplified version - } - } - - private String detectPlatform() { - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("mac")) return "darwin"; - if (os.contains("win")) return "win32"; - return "linux"; - } -} - -// Test execution utilities -class TestExecutor { - - public static void runSingleTest(String testName, ProcessingSketch sketch) { - runSingleTest(testName, sketch, new TestConfig()); - } - - public static void runSingleTest(String testName, ProcessingSketch sketch, TestConfig config) { - // Initialize comparator - PApplet tempApplet = new PApplet(); - ImageComparator comparator = new ImageComparator(tempApplet); - - // Run test - VisualTestRunner tester = new VisualTestRunner(comparator); - TestResult result = tester.runVisualTest(testName, sketch, config); - result.printResult(); - } - - public static void updateSingleBaseline(String testName, ProcessingSketch sketch) { - updateSingleBaseline(testName, sketch, new TestConfig()); - } - - public static void updateSingleBaseline(String testName, ProcessingSketch sketch, TestConfig config) { - // Initialize comparator - PApplet tempApplet = new PApplet(); - ImageComparator comparator = new ImageComparator(tempApplet); - - // Update baseline - VisualTestRunner tester = new VisualTestRunner(comparator); - BaselineManager manager = new BaselineManager(tester); - manager.updateBaseline(testName, sketch, config); - } -} \ No newline at end of file From 5e7f798634e9c0e86655dd702e40d9479194b506 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 03:36:15 +0530 Subject: [PATCH 14/29] added updated screenshot structure --- .../basic-shapes/blue-square-linux.png | Bin 0 -> 3380 bytes .../basic-shapes/custom-size-rect-linux.png | Bin 0 -> 2756 bytes .../basic-shapes/green-circle-linux.png | Bin 0 -> 4449 bytes .../basic-shapes/red-circle-linux.png | Bin 0 -> 4228 bytes .../rendering/linear-gradient-linux.png | Bin 0 -> 2943 bytes 5 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 visual-tests/__screenshots__/basic-shapes/blue-square-linux.png create mode 100644 visual-tests/__screenshots__/basic-shapes/custom-size-rect-linux.png create mode 100644 visual-tests/__screenshots__/basic-shapes/green-circle-linux.png create mode 100644 visual-tests/__screenshots__/basic-shapes/red-circle-linux.png create mode 100644 visual-tests/__screenshots__/rendering/linear-gradient-linux.png diff --git a/visual-tests/__screenshots__/basic-shapes/blue-square-linux.png b/visual-tests/__screenshots__/basic-shapes/blue-square-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e2f49aa29383b0f68da47afcd5d075dd94262884 GIT binary patch literal 3380 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i0*Z)=h^jL%@cj04aSW-5dwXqTuS6IR z>%rGP?|NSOT%TEEu%JbLgHMI=*$1vXl5dauGcW|n|7Kt~@LzcY149xMH-m!OC}T7b zMpMFQE}&K}xKa1#hha=jUEMo%rh2nR)|~jG9}XV<{{H@V;YRz}2bi|SH-5byZh7PV zx3`a-6BwZ)q5Sr5%cUDx_ppd<;9PUsErC%VsFmsUCc%gU-XK-Ga#+MZ2t^!F+n_h9 zY&1YdQ^>&Qg}+>D^8a6H{`-3$r_KY_4GnL@6F$as>LdWO?6;pkf6nb*-~Sd;1~3Q+ vM=&rLHnNT~Mgw6qC5+|*YUP5B`xzevnB6ryCbcywWzv$%l1`qNUh*td zd0EmHFsa1KS;Nz6uHqDxx#xzyvb++G`_EY)B!9Y|pTfbYaWa1YL`9YbD)oQe+5{RV z{rM^_>BQhQDmfYmqbXrDJB*eIqXpw=?J!zJQn#JZ%ePR)bMEkLUg2yZ)(3_QdT|N( RO8{G^44$rjF6*2UngB3MedhoG literal 0 HcmV?d00001 diff --git a/visual-tests/__screenshots__/basic-shapes/green-circle-linux.png b/visual-tests/__screenshots__/basic-shapes/green-circle-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d4434cc2bc691ecc5dd3cc94da012d16a9bca840 GIT binary patch literal 4449 zcmeI0YgCg*8pkIuWCLcaD21$|gnFvk)yQS3Vt^2}pdu8cS`rTmiKwZfP)H-;l905r zY%3RSt3)B-5m^K@#0z48T$Zk&f@Ez7q;e^1j>aSqL=wmb6L$2R_TcxPvtRi7f8Lqr znVJ9m=AHRwcifKm7OhwW0C+EYXXK{}E56lG zaa{~_kg&sJC*ztc`uo+_JCfX!1ucHj>r)T^yE{-;SlHZhX7!a@&AS7I{L9|aM{kW3 zt}f1->m&tFi5_2=3cuDt3D&pFG(>F9>b-#gKcm)$0br~|i2yfG8UPFg3;bf~C~(T1 z2>5{lH1Jp}0ieUx8*C9Se6%nH3ocmL2){QAkG^S4ij6}ZQw?Yr`rc-qwKYI zbQcq>Bnr^^zGbFKQ7h|)e{cQm8AJIvZemAqJbJSSg5A8k$*9S&4%W7QM19PQrCh}i zELo*ONvd7Fzt~bWFy+J6?G;@N{#;Rqe^O?4DP|&CJc89`#~U96Si2vDlIm1jAoogymXpX|DL{{vW2~dcMIujqj4*Zj%Gsb0_iIRL^4o@wrxUu=E@KyzNQZC#j~S*x=@EI4p=Mgp74{a*2S@q`E7+`3 zVL*Xh%e75LfVMRfelgDaH0was;4?`qUfvWqvWQM~_4asqyDIrAmesscY*=rJ>TE-e zRVtBbV*)vJ5A%64saTCOn2JMOwI9I39!ywg4WZ=nhyM}D<5G^b6O?goMEZ=crY6H! zAx&63YpH!5<}Ja&f!EeqE%#p^`l3pHajpzKc2S9()E$6hCoYV8ME!N3vDvLGGU6%~b!vt%5nu9{RT0aS33}Kba1nA=v(PUBr;rS@7#jwV*u5%p3 z!ro=oCcSi1KP%TucM`0Hb#_kW@@~fU*59a7H@+w=4nu4Z_jfXD(tAWVdezwF*{&~x zps603avf|gc}Hhxe881~zosvud%jVJTf=aFGc*@hAlgVesyeIQ|4|MjcZ2R9VAE2X z^1SbWPP(CI*6^%&7O`!iqmo(=wCE*)6LsamX6&d7EYeV9XDr4}@p4HzIf(F}DQ_xF z6Vi1(&kbHBNiLinbW}I=I_WR-1@VCOU7=SaeMi8K^i@a+J>jyenb`or(kwe1L0zXV z)ZsRo7sDz}n>8}h+IitLN9g>pSkwGYSYiKVrRR~Vv)~gP+76UoZJMR8Nk`!Nx!}s*7Ko+kLE?*eQI0!0hCbK z*Mu3bu7WI#w>yz8XgNyDZ2!dNZJ_<}i*I!$+XYdu(P&Lp;!6BE7veIS62VWmKeb7i z^s`FDd#EvQevTJq_S91M;P`-JL#y~OEUOQhANI;9Y#!%$66xzL*=pcu`>vMOK-a0`P9c(LMf}TIq(^ro%(;E1~CXKeVy4d$H=YAO@~a1I9}4Ewx^dJMS*#Z-Ar4Kw81Q$FM)u`GY%s&uHPw8& ztPN=2VBtA~9G={zUqot9myfS6O9HkJA!@XbT1iFs6GXp3pbIjso1K$>wkCx8=r=Va zxxagg&GY}R<|FPu<Z-Tv8j>G7GDTBVL!v|o3wyDScK!%CErSjrT%<(HO8bXA^gD&PIQF5#W{Sn#FKq6 zu2Qv;D~$LQe>{BsmQu*);qP-_#W=DceWa{2YRrpN3!`%ib;X@H$`5bShZsBSLpRo` zc=43m_+iYSRIU;VJf|)tYZ`wsw#2ZH_Fi%bdAliMd}x&modR1;arT!@^odThGsTx> z@rvUL4&`9}I;8+og*hGXU@6QgO(wl@sB%|hd_kUKr(&;S)b>aI*YH>gxL=Vw#dPE} znTH#a28>V6wZ2MqXrEcQ+&TGWJI*oa{L$Iy{K|RL`P6yd**Vz^BJc~>goT^Z!ew#c fzWo1uGH@-^*2-+jN&eLuhZ{@w3; zzdr{0`)X^h(F6c!@7%HN?*ObY2LK^!AP~{l)3*jdch}Bso>g3tn z=D@6oBcFSEZb(`CuOKfD8m7^Iiro14{v&svGr~qJ{`#&WOU!!BGglvVA1ax6VPYGc zp8sIo#GjHagoNv&m>2cI@+g~p8*RY2W;2rjK;lLO56t~27?6dt1q@Oq7U)`W0ccY* z09y-D1Zb=#0}!?Bv`oQr7c2|me?1GF0u;&tl`<}_nwR?c;+9yZS$#HPpu|SrB$YRGB&N} zGB*9r8$7_HDkqdBdZE0pd8s@s8mdc1|2?g!<{f)tV5nMmp<9|Ol}UGF{Li6?|NUnN z!a`$vICAZ8HCc|;^&EBJq>tbWTS`h4_$Yt)v%>#x~G4=YZv`Val6P{T#j79*_y{C z-x$2rSMi`R!j0-i+Ik*ElG10Wf>I?m*&>)3K_KW_av5jsBm2vJ=?&qRMD0DRz$a=3 zXSvM-uLj5RN7}0M0@X224w=~EUU6_Ujm7*K-{i8xRDCE$%^-1Of~0P&N9EV}h&3!M z1O8hn7;ElLQT)@FD@HzH>x|4rW30`&jN$%4-j<9_sfn~N=7@MFyG*Rf{hk`xt3mg# z?E0h(njhm2oGm4ZpB^zD5Mz`<^DAXy{roAI{N_ugR%y3HQ5dUrTm~*KzK#{5(rmoV zYM%~uL9^5hNIw$H?79WUe${#5>gq;CC}MTJR@l*z!`9IuZO+8z#6_12@_yZKYDU?D z;Ee==(zvLygn58(hoNZALsF`l$)<)THHHHuP|MXdE}m19YaAZo6@f+fzzC-*0#w4n zKG8m4;zPms_6;1JnxY?mFfRRE>IHOQ1C+Dbug^IQv+x)v$gYlj=D2D~&_X(Nf@VS6 z0LpQ;LxDAs%41PCn+zC_A!pa_+-cds4(>rn7f@vTz_VBFZRs_3>J&AYu43|Fq`u-f z%^7?Ov#C`lOjmIwtos-vPYUMK6VpQLlQfnMVgyUJO5CHm$c?A@0B@LN+hmetwYZw2 zK{*Z2Y)Gz{z>NimsFAi&Fy0EKs*u|%^1+zEN?1%hldq5Zf%OyO1Z(9j7Ek~4gKh5J zlY%U5j-BQZ$i!+Lo4jI=TN@sshhiX4lcjU7mpnK-+q}g1+?#v1nAwJ$f=PEqW-N^` z`=)$}qb2l17u-(sElrYV&*;wI#bWlHs&I<=G}8hGdLF$7o)rt`&pJ(5W18%KL~p?< zJ?LTKRCy9(!71B~>FhCXykIzbl)kffNO~FQK-r!BZer<`RH`L54R_74&bPX@3siG& zJymA?7d9&b=mF&VCd-9bveqBmKO0v{lfZU({t^2~#l9$hNWluy_lxCYy)T~$^_%Mi zJWF&FadqJ+_MNp5A?n)ioP1$p>D)n6vyHB;_!Rko}Dsv$jq!D zo%W}T6WR+KsjRCjMNTa>FG!{Jx@<((L8x(5u&6o2ZwHj5O3rdi6J~e*7H)b&2T!v2 zpk?0=S{`ah`EH;(jD9}+g6Of70Xks%t9$#Yj`6Q!lW@V=Rpl6y0yqm_$Ocko(#-y5 zm_{ph(L-?>wnMk;>g)s(eX-{|zRl3>_me1kei98yA}}}vukm(rRzti*EGMp{ z+=B(%uTb>g`>Hvmdg9_})t%ge;_KDE43{y(>BmA&VnR~4~9w;TRl06TsBw{g5E G-~R=h2r~2l literal 0 HcmV?d00001 diff --git a/visual-tests/__screenshots__/rendering/linear-gradient-linux.png b/visual-tests/__screenshots__/rendering/linear-gradient-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..87bb74dfc26bbc8a7eb9abf7333500b87bec0a24 GIT binary patch literal 2943 zcmX|DYgCh07X5gGlvinWKoF%B%OFru&?pEhh)gx8ASq!13ItjrVu`3pggAwTIyj<& z*aQe_tC;ed6$})H;Hp?uB%mW920|Q%m*rW3AXFZ6K6Bt_Zoa$sKKtx@?!B;|8fdo6 zb{RnsW-dBTTy6O>8Z%TG?M zPaaPr-Z)1qrlSXph-bORI^QH&zNXErAtpcHNzk9tJb%4DV+H%%nGf8@V{xB*;;ps` zYuHz;cv#H%2v^$@uLe(qkgFX(7564Ap*@fEhfIgeL0E$uo!oN zeGVOYV*?89K39kK(6PSF#Iq1}rRbIHhb6RaHl_sU<26KipH$F4a?yFX9umCr86 z)gr;M6X`~46=&gu}H<~M@3nprFDs+ptRHD4XS;8y`#;zFBr&I-rl z&DgFzgkLcG$9Rq>&+x3!-`J1Imd2;A<-y+`KgY8vJOf-Uk56;v>A|K%*;LHDIpK4` z@@$8pRl{W-32-}}w|zk$KdtRQ?Y5wcKScd!ycPg@JVvJ3JFlnRH@;IgcVZdAvGt!0 zZCYe#=x2NMfIfCbf0|>Do?oz|{tbzD#DtH~gt)QJO7|IED4KD2nh7QeD-2f2{L#8# z`S&?-F~pm1EnheC_0fSR^fx#6E(2dK*#3Fg33Pubt$um>>fA--uxwSaGjXHDo1U${ z?yRGwSS9*xR2E7+d$IZf*TWMV*(jOr<{N^YyCe@I`HR;7N*{0C3)YiHjQhE!2Hr6` zD@@_Jrg7s5EnjVAey!_9UBBQA<7Am7_MgjCMtE35D!7TY1sgXaMYm*jAKw7QSI}QK zwUFXl>2I@I47C&~f#sV#sz7{(ab=qw)?TA%Rxqd#rW*NQ74dFJ5y6x*JvJkM?&$nXY)MQ`MrJnd zj{5?H4>4cfwjt%!(Fd-s{|MzJm2w2rI+Mi7d2N&Jyj^$CjZlf0#b+mZn}eKH?F$?nybc~M-Rf}1u63WWSV%!FIH zto89YP`HU?!EMWsVufOP2_pcJvlE|&RDDcLZaV)*UfH7DBT1<#VSt>nBPs1*7-o=i zznZe46f#JutoB(`ioO&^blDesYWX@>Kj;xVfeEa{_pw#WQLa+bxFLKccpNV2d2X}V zu=tS^p&cZ}?vXQb9g7swEQc$aC9uY-?Ev;7@p_|J2f;*=p_@ue0CSdftx+0d!+JaC z{(BU}_x;q_bK|HknjSO4KJ10a8O9cm!|y zE+8Xg@VvcvCD#A<)ZQtjJ=Cw0+I~{0r`5w)*vjb|HX#G6a^j+%VcMgkMN-V#RC#Po z^LCIiCF-eeX+RlV#oLHDUm)hnO$ritNbEE8P?kcFi8%hk&RFZEII!qmkU5SmrIf1P zVg$OCK{H>l61Kd>(5gBJ~RNe!TjM+bW!fW*4S=4kV_5+ zaY#`u^%5Y9V~af32V$y*_a{^q#lmSj8a+^UEf%Y&E@dfNzWXqLJX5^Uk?K_SAO*%b z*%(H_R-U8~G&6?k4$BMmk>XI~FMp%Lm=!Z3@1;=j#AF!LnnmD9_1$b*%eUxAfnu#o z?+G+QHu#@DmLYUtAM;@f#TqGc>y9c54S=Gj?hL;W?i}r`cPnC%azknBudz_BSDDN8 z7B(WUY(Ml>?-}rVNFmQ2&_xQ(py0O@xM1^AhdKjTaJiBUYg&t#*sw5BSAVkvMY||QSp8c;w2R!H)yvU}_Bwxj>LOfc z;+Sqq#4()a;WtMGo|{REhEd_#%}BAH_O7L{0PV;x=O#3}g7AFN`>fuuC9ozH%ml{o zb-XMU-kjo4pD)u!0a+a9vn6sAug>sWs{~%B7LlVYf^+{3;@5}e>@DIhjSNwWJ2(%Ae}j8V=Jl{X|s8;~#|wA6zp zoi6sqi{2waIyvTT3SVC0%tW)zQP~uyub7RG6-G5jUGY zx78@>acFz6r1RJ7VfHcZaC+Lm=SnWZfitw4-%yFQ zw(L)yTayeYnctwEPgA2W{0j9< Date: Tue, 7 Oct 2025 03:38:06 +0530 Subject: [PATCH 15/29] refactoring --- .../test/visual/BaselineManager.java | 54 +++++++++++++++++++ .../test/visual/ProcessingSketch.java | 9 ++++ .../processing/test/visual/TestConfig.java | 33 ++++++++++++ .../processing/test/visual/TestResult.java | 45 ++++++++++++++++ 4 files changed, 141 insertions(+) create mode 100644 visual-tests/src/main/java/processing/test/visual/BaselineManager.java create mode 100644 visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java create mode 100644 visual-tests/src/main/java/processing/test/visual/TestConfig.java create mode 100644 visual-tests/src/main/java/processing/test/visual/TestResult.java diff --git a/visual-tests/src/main/java/processing/test/visual/BaselineManager.java b/visual-tests/src/main/java/processing/test/visual/BaselineManager.java new file mode 100644 index 0000000000..a9d1186dc3 --- /dev/null +++ b/visual-tests/src/main/java/processing/test/visual/BaselineManager.java @@ -0,0 +1,54 @@ +package processing.test.visual; + +import processing.core.PImage; + +import java.util.List; + +// Baseline manager for updating reference images +public class BaselineManager { + private VisualTestRunner tester; + + public BaselineManager(VisualTestRunner tester) { + this.tester = tester; + } + + public void updateBaseline(String testName, ProcessingSketch sketch) { + updateBaseline(testName, sketch, new TestConfig()); + } + + public void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + System.out.println("Updating baseline for: " + testName); + + // Capture new image + SketchRunner runner = new SketchRunner(sketch, config); + runner.run(); + PImage newImage = runner.getImage(); + + // Save as baseline + String baselinePath = "__screenshots__/" + + testName.replaceAll("[^a-zA-Z0-9-_]", "-") + + "-" + detectPlatform() + ".png"; + newImage.save(baselinePath); + + System.out.println("Baseline updated: " + baselinePath); + } + + public void updateAllBaselines(ProcessingTestSuite suite) { + System.out.println("Updating all baselines..."); + List testNames = suite.getTestNames(); + + for (String testName : testNames) { + // Re-run the test to get the sketch and config + TestResult result = suite.runTest(testName); + // Note: In a real implementation, you'd need to store the sketch reference + // This is a simplified version + } + } + + private String detectPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) return "darwin"; + if (os.contains("win")) return "win32"; + return "linux"; + } +} diff --git a/visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java b/visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java new file mode 100644 index 0000000000..c879ffbb1e --- /dev/null +++ b/visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java @@ -0,0 +1,9 @@ +package processing.test.visual; + +import processing.core.PApplet; + +// Interface for user sketches +public interface ProcessingSketch { + void setup(PApplet p); + void draw(PApplet p); +} diff --git a/visual-tests/src/main/java/processing/test/visual/TestConfig.java b/visual-tests/src/main/java/processing/test/visual/TestConfig.java new file mode 100644 index 0000000000..ff69b2e75f --- /dev/null +++ b/visual-tests/src/main/java/processing/test/visual/TestConfig.java @@ -0,0 +1,33 @@ +package processing.test.visual; + +// Test configuration class +public class TestConfig { + public int width = 800; + public int height = 600; + public int[] backgroundColor = {255, 255, 255}; // RGB + public long renderWaitTime = 100; // milliseconds + public double threshold = 0.1; + + public TestConfig() {} + + public TestConfig(int width, int height) { + this.width = width; + this.height = height; + } + + public TestConfig(int width, int height, int[] backgroundColor) { + this.width = width; + this.height = height; + this.backgroundColor = backgroundColor; + } + + public TestConfig setThreshold(double threshold) { + this.threshold = threshold; + return this; + } + + public TestConfig setRenderWaitTime(long waitTime) { + this.renderWaitTime = waitTime; + return this; + } +} diff --git a/visual-tests/src/main/java/processing/test/visual/TestResult.java b/visual-tests/src/main/java/processing/test/visual/TestResult.java new file mode 100644 index 0000000000..139e57561f --- /dev/null +++ b/visual-tests/src/main/java/processing/test/visual/TestResult.java @@ -0,0 +1,45 @@ +package processing.test.visual; + +// Enhanced test result with detailed information +public class TestResult { + public String testName; + public boolean passed; + public double mismatchRatio; + public String error; + public boolean isFirstRun; + public ComparisonDetails details; + + public TestResult(String testName, ComparisonResult comparison) { + this.testName = testName; + this.passed = comparison.passed; + this.mismatchRatio = comparison.mismatchRatio; + this.isFirstRun = comparison.isFirstRun; + this.details = comparison.details; + } + + public static TestResult createError(String testName, String error) { + TestResult result = new TestResult(); + result.testName = testName; + result.passed = false; + result.error = error; + return result; + } + + private TestResult() {} // For error constructor + + public void printResult() { + System.out.print(testName + ": "); + if (error != null) { + System.out.println("ERROR - " + error); + } else if (isFirstRun) { + System.out.println("BASELINE CREATED"); + } else if (passed) { + System.out.println("PASSED"); + } else { + System.out.println("FAILED (mismatch: " + String.format("%.4f", mismatchRatio * 100) + "%)"); + if (details != null) { + details.printDetails(); + } + } + } +} From 4fcd95b535141d85c246366706c29c55c805d0e9 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 03:38:45 +0530 Subject: [PATCH 16/29] added tests in suits --- .../src/test/java/visual/base/VisualTest.java | 61 ++++++++++++ .../java/visual/rendering/GradientTest.java | 35 +++++++ .../java/visual/shapes/BasicShapeTest.java | 92 +++++++++++++++++++ .../java/visual/suites/BasicShapesSuite.java | 11 +++ .../java/visual/suites/RenderingSuite.java | 11 +++ 5 files changed, 210 insertions(+) create mode 100644 visual-tests/src/test/java/visual/base/VisualTest.java create mode 100644 visual-tests/src/test/java/visual/rendering/GradientTest.java create mode 100644 visual-tests/src/test/java/visual/shapes/BasicShapeTest.java create mode 100644 visual-tests/src/test/java/visual/suites/BasicShapesSuite.java create mode 100644 visual-tests/src/test/java/visual/suites/RenderingSuite.java diff --git a/visual-tests/src/test/java/visual/base/VisualTest.java b/visual-tests/src/test/java/visual/base/VisualTest.java new file mode 100644 index 0000000000..e574a5b6f7 --- /dev/null +++ b/visual-tests/src/test/java/visual/base/VisualTest.java @@ -0,0 +1,61 @@ +package visual.base; + +import org.junit.jupiter.api.*; +import processing.core.*; +import static org.junit.jupiter.api.Assertions.*; +import processing.test.visual.*; +import java.nio.file.*; +import java.io.File; + +/** + * Base class for Processing visual tests using JUnit 5 + */ +public abstract class VisualTest { + + protected static VisualTestRunner testRunner; + protected static ImageComparator comparator; + + @BeforeAll + public static void setupTestRunner() { + PApplet tempApplet = new PApplet(); + comparator = new ImageComparator(tempApplet); + testRunner = new VisualTestRunner(comparator); + + System.out.println("Visual test runner initialized"); + } + + /** + * Helper method to run a visual test + */ + protected void assertVisualMatch(String testName, ProcessingSketch sketch) { + assertVisualMatch(testName, sketch, new TestConfig()); + } + + protected void assertVisualMatch(String testName, ProcessingSketch sketch, TestConfig config) { + TestResult result = testRunner.runVisualTest(testName, sketch, config); + + // Print result for debugging + result.printResult(); + + // Handle different result types + if (result.isFirstRun) { + // First run - baseline created, mark as skipped + Assumptions.assumeTrue(false, "Baseline created for " + testName + ". Run tests again to verify."); + } else if (result.error != null) { + fail("Test error: " + result.error); + } else { + // Assert that the test passed + Assertions.assertTrue(result.passed, + String.format("Visual test '%s' failed with mismatch ratio: %.4f%%", + testName, result.mismatchRatio * 100)); + } + } + + /** + * Update baseline for a specific test (useful for maintenance) + */ + protected void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + BaselineManager manager = new BaselineManager(testRunner); + manager.updateBaseline(testName, sketch, config); + } +} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/rendering/GradientTest.java b/visual-tests/src/test/java/visual/rendering/GradientTest.java new file mode 100644 index 0000000000..479d0872b1 --- /dev/null +++ b/visual-tests/src/test/java/visual/rendering/GradientTest.java @@ -0,0 +1,35 @@ +package visual.rendering; + +import org.junit.jupiter.api.*; +import processing.core.*; +import visual.base.VisualTest; +import processing.test.visual.ProcessingSketch; +import processing.test.visual.TestConfig; + +@Tag("rendering") +@Tag("gradients") +public class GradientTest extends VisualTest { + + @Test + @DisplayName("Linear gradient renders correctly") + public void testLinearGradient() { + TestConfig config = new TestConfig(600, 400); + + assertVisualMatch("rendering/linear-gradient", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.noStroke(); + } + + @Override + public void draw(PApplet p) { + for (int y = 0; y < p.height; y++) { + float inter = PApplet.map(y, 0, p.height, 0, 1); + int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); + p.stroke(c); + p.line(0, y, p.width, y); + } + } + }, config); + } +} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/shapes/BasicShapeTest.java b/visual-tests/src/test/java/visual/shapes/BasicShapeTest.java new file mode 100644 index 0000000000..6756b2557e --- /dev/null +++ b/visual-tests/src/test/java/visual/shapes/BasicShapeTest.java @@ -0,0 +1,92 @@ +package visual.shapes; + + +import org.junit.jupiter.api.*; +import processing.core.*; +import visual.base.*; +import processing.test.visual.*; + +@Tag("basic") +@Tag("shapes") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BasicShapeTest extends VisualTest { + + @Test + @Order(1) + @DisplayName("Red circle renders correctly") + public void testRedCircle() { + assertVisualMatch("basic-shapes/red-circle", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.noStroke(); + } + + @Override + public void draw(PApplet p) { + p.background(255, 255, 255); + p.fill(255, 0, 0); + p.ellipse(p.width/2, p.height/2, 100, 100); + } + }); + } + + @Test + @Order(2) + @DisplayName("Blue square renders correctly") + public void testBlueSquare() { + assertVisualMatch("basic-shapes/blue-square", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.stroke(0); + p.strokeWeight(2); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.fill(0, 0, 255); + p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); + } + }); + } + + @Test + @Order(3) + @DisplayName("Green circle renders correctly") + public void testGreenCircle() { + assertVisualMatch("basic-shapes/green-circle", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.noStroke(); + } + + @Override + public void draw(PApplet p) { + p.background(255, 255, 255); + p.fill(0, 255, 0); + p.ellipse(p.width/2, p.height/2, 100, 100); + } + }); + } + + @Test + @Order(4) + @DisplayName("Custom size canvas") + public void testCustomSize() { + TestConfig config = new TestConfig(600, 400); + + assertVisualMatch("basic-shapes/custom-size-rect", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.noStroke(); + } + + @Override + public void draw(PApplet p) { + p.background(240, 240, 240); + p.fill(128, 0, 128); + p.rect(50, 50, p.width - 100, p.height - 100); + } + }, config); + } +} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/suites/BasicShapesSuite.java b/visual-tests/src/test/java/visual/suites/BasicShapesSuite.java new file mode 100644 index 0000000000..7145e04156 --- /dev/null +++ b/visual-tests/src/test/java/visual/suites/BasicShapesSuite.java @@ -0,0 +1,11 @@ +package visual.suites; + +import org.junit.platform.suite.api.*; + +@Suite +@SuiteDisplayName("Basic Shapes Visual Tests") +@SelectPackages("visual.shapes") +@IncludeTags("basic") +public class BasicShapesSuite { + // Empty class - just holds annotations +} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/suites/RenderingSuite.java b/visual-tests/src/test/java/visual/suites/RenderingSuite.java new file mode 100644 index 0000000000..6637cd06dc --- /dev/null +++ b/visual-tests/src/test/java/visual/suites/RenderingSuite.java @@ -0,0 +1,11 @@ +package visual.suites; + +import org.junit.platform.suite.api.*; + +@Suite +@SuiteDisplayName("Rendering Tests") +@SelectPackages("processing.test.visual.rendering") +@IncludeTags("rendering") +public class RenderingSuite { + // Empty class - just holds annotations +} From f778c9903df41e81e7e33fcaf0f9461a0b825967 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 7 Oct 2025 11:31:51 +0530 Subject: [PATCH 17/29] removed simple test --- visual-tests/src/main/java/SimpleTest.java | 105 --------------------- 1 file changed, 105 deletions(-) delete mode 100644 visual-tests/src/main/java/SimpleTest.java diff --git a/visual-tests/src/main/java/SimpleTest.java b/visual-tests/src/main/java/SimpleTest.java deleted file mode 100644 index ae31df988c..0000000000 --- a/visual-tests/src/main/java/SimpleTest.java +++ /dev/null @@ -1,105 +0,0 @@ -//// SimpleTest.java - Fixed version for quick verification -//import processing.core.*; -//import java.util.*; -// -//public class SimpleTest { -// -// public static void main(String[] args) { -// System.out.println("=== Processing Visual Testing - Quick Test ===\n"); -// -// try { -// PApplet tempApplet = new PApplet(); -// -// ImageComparator comparator = new ImageComparator(tempApplet); -// VisualTestRunner tester = new VisualTestRunner(comparator); -// -// ProcessingSketch redCircle = new SimpleRedCircle(); -// TestResult result1 = tester.runVisualTest("red-circle", redCircle); -// result1.printResult(); -// -// ProcessingSketch blueSquare = new SimpleBlueSquare(); -// TestResult result2 = tester.runVisualTest("blue-square", blueSquare); -// result2.printResult(); -// -// // Step 4: Test comparison with identical image (should pass on second run) -// if (!result1.isFirstRun) { -// TestResult result3 = tester.runVisualTest("red-circle", redCircle); -// result3.printResult(); -// -// if (result3.passed) { -// System.out.println("✓ Identical image comparison works!"); -// } else { -// System.out.println("✗ Identical image comparison failed!"); -// } -// } -// ProcessingTestSuite suite = new ProcessingTestSuite(tester); -// suite.addTest("suite-red", new SimpleRedCircle()); -// suite.addTest("suite-blue", new SimpleBlueSquare()); -// suite.addTest("suite-gradient", new SimpleGradient()); -// -// List suiteResults = suite.runAll(); -// -// long passed = suiteResults.stream().filter(r -> r.passed).count(); -// long total = suiteResults.size(); -// System.out.println("Suite results: " + passed + "/" + total + " passed"); -// System.exit(0); -// -// } catch (Exception e) { -// System.err.println("Test failed with error: " + e.getMessage()); -// e.printStackTrace(); -// } -// } -// -// // Simple test sketches -// static class SimpleRedCircle implements ProcessingSketch { -// public void setup(PApplet p) { -// p.noStroke(); -// } -// -// public void draw(PApplet p) { -// p.background(255, 255, 255); -// p.fill(255, 0, 0); -// p.ellipse(p.width/2, p.height/2, 100, 100); -// } -// } -// -// static class SimpleBlueSquare implements ProcessingSketch { -// public void setup(PApplet p) { -// p.stroke(0); -// p.strokeWeight(2); -// } -// -// public void draw(PApplet p) { -// p.background(255); -// p.fill(0, 0, 255); -// p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); -// } -// } -// -// static class SimpleGreenCircle implements ProcessingSketch { -// public void setup(PApplet p) { -// p.noStroke(); -// } -// -// public void draw(PApplet p) { -// p.background(255, 255, 255); -// p.fill(0, 255, 0); // Green instead of red -// p.ellipse(p.width/2, p.height/2, 100, 100); -// } -// } -// -// static class SimpleGradient implements ProcessingSketch { -// public void setup(PApplet p) { -// p.noStroke(); -// } -// -// public void draw(PApplet p) { -// for (int y = 0; y < p.height; y++) { -// float inter = PApplet.map(y, 0, p.height, 0, 1); -// int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); -// p.stroke(c); -// p.line(0, y, p.width, y); -// } -// } -// } -//} \ No newline at end of file From 6224f8c64e9a5fdf9500cd381728ca84f5e5359d Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 16 Oct 2025 16:51:46 +0530 Subject: [PATCH 18/29] deleting earlier files --- .../basic-shapes/blue-square-linux.png | Bin 3380 -> 0 bytes .../basic-shapes/custom-size-rect-linux.png | Bin 2756 -> 0 bytes .../basic-shapes/green-circle-linux.png | Bin 4449 -> 0 bytes .../basic-shapes/red-circle-linux.png | Bin 4228 -> 0 bytes .../rendering/linear-gradient-linux.png | Bin 2943 -> 0 bytes visual-tests/build.gradle.kts | 227 ---------- .../test/visual/BaselineManager.java | 54 --- .../test/visual/ImageComparator.java | 417 ------------------ .../test/visual/ProcessingSketch.java | 9 - .../processing/test/visual/TestConfig.java | 33 -- .../processing/test/visual/TestResult.java | 45 -- .../test/visual/VisualTestRunner.java | 321 -------------- .../src/test/java/visual/base/VisualTest.java | 61 --- .../java/visual/rendering/GradientTest.java | 35 -- .../java/visual/shapes/BasicShapeTest.java | 92 ---- .../java/visual/suites/BasicShapesSuite.java | 11 - .../java/visual/suites/RenderingSuite.java | 11 - 17 files changed, 1316 deletions(-) delete mode 100644 visual-tests/__screenshots__/basic-shapes/blue-square-linux.png delete mode 100644 visual-tests/__screenshots__/basic-shapes/custom-size-rect-linux.png delete mode 100644 visual-tests/__screenshots__/basic-shapes/green-circle-linux.png delete mode 100644 visual-tests/__screenshots__/basic-shapes/red-circle-linux.png delete mode 100644 visual-tests/__screenshots__/rendering/linear-gradient-linux.png delete mode 100644 visual-tests/build.gradle.kts delete mode 100644 visual-tests/src/main/java/processing/test/visual/BaselineManager.java delete mode 100644 visual-tests/src/main/java/processing/test/visual/ImageComparator.java delete mode 100644 visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java delete mode 100644 visual-tests/src/main/java/processing/test/visual/TestConfig.java delete mode 100644 visual-tests/src/main/java/processing/test/visual/TestResult.java delete mode 100644 visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java delete mode 100644 visual-tests/src/test/java/visual/base/VisualTest.java delete mode 100644 visual-tests/src/test/java/visual/rendering/GradientTest.java delete mode 100644 visual-tests/src/test/java/visual/shapes/BasicShapeTest.java delete mode 100644 visual-tests/src/test/java/visual/suites/BasicShapesSuite.java delete mode 100644 visual-tests/src/test/java/visual/suites/RenderingSuite.java diff --git a/visual-tests/__screenshots__/basic-shapes/blue-square-linux.png b/visual-tests/__screenshots__/basic-shapes/blue-square-linux.png deleted file mode 100644 index e2f49aa29383b0f68da47afcd5d075dd94262884..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3380 zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i0*Z)=h^jL%@cj04aSW-5dwXqTuS6IR z>%rGP?|NSOT%TEEu%JbLgHMI=*$1vXl5dauGcW|n|7Kt~@LzcY149xMH-m!OC}T7b zMpMFQE}&K}xKa1#hha=jUEMo%rh2nR)|~jG9}XV<{{H@V;YRz}2bi|SH-5byZh7PV zx3`a-6BwZ)q5Sr5%cUDx_ppd<;9PUsErC%VsFmsUCc%gU-XK-Ga#+MZ2t^!F+n_h9 zY&1YdQ^>&Qg}+>D^8a6H{`-3$r_KY_4GnL@6F$as>LdWO?6;pkf6nb*-~Sd;1~3Q+ vM=&rLHnNT~Mgw6qC5+|*YUP5B`xzevnB6ryCbcywWzv$%l1`qNUh*td zd0EmHFsa1KS;Nz6uHqDxx#xzyvb++G`_EY)B!9Y|pTfbYaWa1YL`9YbD)oQe+5{RV z{rM^_>BQhQDmfYmqbXrDJB*eIqXpw=?J!zJQn#JZ%ePR)bMEkLUg2yZ)(3_QdT|N( RO8{G^44$rjF6*2UngB3MedhoG diff --git a/visual-tests/__screenshots__/basic-shapes/green-circle-linux.png b/visual-tests/__screenshots__/basic-shapes/green-circle-linux.png deleted file mode 100644 index d4434cc2bc691ecc5dd3cc94da012d16a9bca840..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4449 zcmeI0YgCg*8pkIuWCLcaD21$|gnFvk)yQS3Vt^2}pdu8cS`rTmiKwZfP)H-;l905r zY%3RSt3)B-5m^K@#0z48T$Zk&f@Ez7q;e^1j>aSqL=wmb6L$2R_TcxPvtRi7f8Lqr znVJ9m=AHRwcifKm7OhwW0C+EYXXK{}E56lG zaa{~_kg&sJC*ztc`uo+_JCfX!1ucHj>r)T^yE{-;SlHZhX7!a@&AS7I{L9|aM{kW3 zt}f1->m&tFi5_2=3cuDt3D&pFG(>F9>b-#gKcm)$0br~|i2yfG8UPFg3;bf~C~(T1 z2>5{lH1Jp}0ieUx8*C9Se6%nH3ocmL2){QAkG^S4ij6}ZQw?Yr`rc-qwKYI zbQcq>Bnr^^zGbFKQ7h|)e{cQm8AJIvZemAqJbJSSg5A8k$*9S&4%W7QM19PQrCh}i zELo*ONvd7Fzt~bWFy+J6?G;@N{#;Rqe^O?4DP|&CJc89`#~U96Si2vDlIm1jAoogymXpX|DL{{vW2~dcMIujqj4*Zj%Gsb0_iIRL^4o@wrxUu=E@KyzNQZC#j~S*x=@EI4p=Mgp74{a*2S@q`E7+`3 zVL*Xh%e75LfVMRfelgDaH0was;4?`qUfvWqvWQM~_4asqyDIrAmesscY*=rJ>TE-e zRVtBbV*)vJ5A%64saTCOn2JMOwI9I39!ywg4WZ=nhyM}D<5G^b6O?goMEZ=crY6H! zAx&63YpH!5<}Ja&f!EeqE%#p^`l3pHajpzKc2S9()E$6hCoYV8ME!N3vDvLGGU6%~b!vt%5nu9{RT0aS33}Kba1nA=v(PUBr;rS@7#jwV*u5%p3 z!ro=oCcSi1KP%TucM`0Hb#_kW@@~fU*59a7H@+w=4nu4Z_jfXD(tAWVdezwF*{&~x zps603avf|gc}Hhxe881~zosvud%jVJTf=aFGc*@hAlgVesyeIQ|4|MjcZ2R9VAE2X z^1SbWPP(CI*6^%&7O`!iqmo(=wCE*)6LsamX6&d7EYeV9XDr4}@p4HzIf(F}DQ_xF z6Vi1(&kbHBNiLinbW}I=I_WR-1@VCOU7=SaeMi8K^i@a+J>jyenb`or(kwe1L0zXV z)ZsRo7sDz}n>8}h+IitLN9g>pSkwGYSYiKVrRR~Vv)~gP+76UoZJMR8Nk`!Nx!}s*7Ko+kLE?*eQI0!0hCbK z*Mu3bu7WI#w>yz8XgNyDZ2!dNZJ_<}i*I!$+XYdu(P&Lp;!6BE7veIS62VWmKeb7i z^s`FDd#EvQevTJq_S91M;P`-JL#y~OEUOQhANI;9Y#!%$66xzL*=pcu`>vMOK-a0`P9c(LMf}TIq(^ro%(;E1~CXKeVy4d$H=YAO@~a1I9}4Ewx^dJMS*#Z-Ar4Kw81Q$FM)u`GY%s&uHPw8& ztPN=2VBtA~9G={zUqot9myfS6O9HkJA!@XbT1iFs6GXp3pbIjso1K$>wkCx8=r=Va zxxagg&GY}R<|FPu<Z-Tv8j>G7GDTBVL!v|o3wyDScK!%CErSjrT%<(HO8bXA^gD&PIQF5#W{Sn#FKq6 zu2Qv;D~$LQe>{BsmQu*);qP-_#W=DceWa{2YRrpN3!`%ib;X@H$`5bShZsBSLpRo` zc=43m_+iYSRIU;VJf|)tYZ`wsw#2ZH_Fi%bdAliMd}x&modR1;arT!@^odThGsTx> z@rvUL4&`9}I;8+og*hGXU@6QgO(wl@sB%|hd_kUKr(&;S)b>aI*YH>gxL=Vw#dPE} znTH#a28>V6wZ2MqXrEcQ+&TGWJI*oa{L$Iy{K|RL`P6yd**Vz^BJc~>goT^Z!ew#c fzWo1uGH@-^*2-+jN&eLuhZ{@w3; zzdr{0`)X^h(F6c!@7%HN?*ObY2LK^!AP~{l)3*jdch}Bso>g3tn z=D@6oBcFSEZb(`CuOKfD8m7^Iiro14{v&svGr~qJ{`#&WOU!!BGglvVA1ax6VPYGc zp8sIo#GjHagoNv&m>2cI@+g~p8*RY2W;2rjK;lLO56t~27?6dt1q@Oq7U)`W0ccY* z09y-D1Zb=#0}!?Bv`oQr7c2|me?1GF0u;&tl`<}_nwR?c;+9yZS$#HPpu|SrB$YRGB&N} zGB*9r8$7_HDkqdBdZE0pd8s@s8mdc1|2?g!<{f)tV5nMmp<9|Ol}UGF{Li6?|NUnN z!a`$vICAZ8HCc|;^&EBJq>tbWTS`h4_$Yt)v%>#x~G4=YZv`Val6P{T#j79*_y{C z-x$2rSMi`R!j0-i+Ik*ElG10Wf>I?m*&>)3K_KW_av5jsBm2vJ=?&qRMD0DRz$a=3 zXSvM-uLj5RN7}0M0@X224w=~EUU6_Ujm7*K-{i8xRDCE$%^-1Of~0P&N9EV}h&3!M z1O8hn7;ElLQT)@FD@HzH>x|4rW30`&jN$%4-j<9_sfn~N=7@MFyG*Rf{hk`xt3mg# z?E0h(njhm2oGm4ZpB^zD5Mz`<^DAXy{roAI{N_ugR%y3HQ5dUrTm~*KzK#{5(rmoV zYM%~uL9^5hNIw$H?79WUe${#5>gq;CC}MTJR@l*z!`9IuZO+8z#6_12@_yZKYDU?D z;Ee==(zvLygn58(hoNZALsF`l$)<)THHHHuP|MXdE}m19YaAZo6@f+fzzC-*0#w4n zKG8m4;zPms_6;1JnxY?mFfRRE>IHOQ1C+Dbug^IQv+x)v$gYlj=D2D~&_X(Nf@VS6 z0LpQ;LxDAs%41PCn+zC_A!pa_+-cds4(>rn7f@vTz_VBFZRs_3>J&AYu43|Fq`u-f z%^7?Ov#C`lOjmIwtos-vPYUMK6VpQLlQfnMVgyUJO5CHm$c?A@0B@LN+hmetwYZw2 zK{*Z2Y)Gz{z>NimsFAi&Fy0EKs*u|%^1+zEN?1%hldq5Zf%OyO1Z(9j7Ek~4gKh5J zlY%U5j-BQZ$i!+Lo4jI=TN@sshhiX4lcjU7mpnK-+q}g1+?#v1nAwJ$f=PEqW-N^` z`=)$}qb2l17u-(sElrYV&*;wI#bWlHs&I<=G}8hGdLF$7o)rt`&pJ(5W18%KL~p?< zJ?LTKRCy9(!71B~>FhCXykIzbl)kffNO~FQK-r!BZer<`RH`L54R_74&bPX@3siG& zJymA?7d9&b=mF&VCd-9bveqBmKO0v{lfZU({t^2~#l9$hNWluy_lxCYy)T~$^_%Mi zJWF&FadqJ+_MNp5A?n)ioP1$p>D)n6vyHB;_!Rko}Dsv$jq!D zo%W}T6WR+KsjRCjMNTa>FG!{Jx@<((L8x(5u&6o2ZwHj5O3rdi6J~e*7H)b&2T!v2 zpk?0=S{`ah`EH;(jD9}+g6Of70Xks%t9$#Yj`6Q!lW@V=Rpl6y0yqm_$Ocko(#-y5 zm_{ph(L-?>wnMk;>g)s(eX-{|zRl3>_me1kei98yA}}}vukm(rRzti*EGMp{ z+=B(%uTb>g`>Hvmdg9_})t%ge;_KDE43{y(>BmA&VnR~4~9w;TRl06TsBw{g5E G-~R=h2r~2l diff --git a/visual-tests/__screenshots__/rendering/linear-gradient-linux.png b/visual-tests/__screenshots__/rendering/linear-gradient-linux.png deleted file mode 100644 index 87bb74dfc26bbc8a7eb9abf7333500b87bec0a24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2943 zcmX|DYgCh07X5gGlvinWKoF%B%OFru&?pEhh)gx8ASq!13ItjrVu`3pggAwTIyj<& z*aQe_tC;ed6$})H;Hp?uB%mW920|Q%m*rW3AXFZ6K6Bt_Zoa$sKKtx@?!B;|8fdo6 zb{RnsW-dBTTy6O>8Z%TG?M zPaaPr-Z)1qrlSXph-bORI^QH&zNXErAtpcHNzk9tJb%4DV+H%%nGf8@V{xB*;;ps` zYuHz;cv#H%2v^$@uLe(qkgFX(7564Ap*@fEhfIgeL0E$uo!oN zeGVOYV*?89K39kK(6PSF#Iq1}rRbIHhb6RaHl_sU<26KipH$F4a?yFX9umCr86 z)gr;M6X`~46=&gu}H<~M@3nprFDs+ptRHD4XS;8y`#;zFBr&I-rl z&DgFzgkLcG$9Rq>&+x3!-`J1Imd2;A<-y+`KgY8vJOf-Uk56;v>A|K%*;LHDIpK4` z@@$8pRl{W-32-}}w|zk$KdtRQ?Y5wcKScd!ycPg@JVvJ3JFlnRH@;IgcVZdAvGt!0 zZCYe#=x2NMfIfCbf0|>Do?oz|{tbzD#DtH~gt)QJO7|IED4KD2nh7QeD-2f2{L#8# z`S&?-F~pm1EnheC_0fSR^fx#6E(2dK*#3Fg33Pubt$um>>fA--uxwSaGjXHDo1U${ z?yRGwSS9*xR2E7+d$IZf*TWMV*(jOr<{N^YyCe@I`HR;7N*{0C3)YiHjQhE!2Hr6` zD@@_Jrg7s5EnjVAey!_9UBBQA<7Am7_MgjCMtE35D!7TY1sgXaMYm*jAKw7QSI}QK zwUFXl>2I@I47C&~f#sV#sz7{(ab=qw)?TA%Rxqd#rW*NQ74dFJ5y6x*JvJkM?&$nXY)MQ`MrJnd zj{5?H4>4cfwjt%!(Fd-s{|MzJm2w2rI+Mi7d2N&Jyj^$CjZlf0#b+mZn}eKH?F$?nybc~M-Rf}1u63WWSV%!FIH zto89YP`HU?!EMWsVufOP2_pcJvlE|&RDDcLZaV)*UfH7DBT1<#VSt>nBPs1*7-o=i zznZe46f#JutoB(`ioO&^blDesYWX@>Kj;xVfeEa{_pw#WQLa+bxFLKccpNV2d2X}V zu=tS^p&cZ}?vXQb9g7swEQc$aC9uY-?Ev;7@p_|J2f;*=p_@ue0CSdftx+0d!+JaC z{(BU}_x;q_bK|HknjSO4KJ10a8O9cm!|y zE+8Xg@VvcvCD#A<)ZQtjJ=Cw0+I~{0r`5w)*vjb|HX#G6a^j+%VcMgkMN-V#RC#Po z^LCIiCF-eeX+RlV#oLHDUm)hnO$ritNbEE8P?kcFi8%hk&RFZEII!qmkU5SmrIf1P zVg$OCK{H>l61Kd>(5gBJ~RNe!TjM+bW!fW*4S=4kV_5+ zaY#`u^%5Y9V~af32V$y*_a{^q#lmSj8a+^UEf%Y&E@dfNzWXqLJX5^Uk?K_SAO*%b z*%(H_R-U8~G&6?k4$BMmk>XI~FMp%Lm=!Z3@1;=j#AF!LnnmD9_1$b*%eUxAfnu#o z?+G+QHu#@DmLYUtAM;@f#TqGc>y9c54S=Gj?hL;W?i}r`cPnC%azknBudz_BSDDN8 z7B(WUY(Ml>?-}rVNFmQ2&_xQ(py0O@xM1^AhdKjTaJiBUYg&t#*sw5BSAVkvMY||QSp8c;w2R!H)yvU}_Bwxj>LOfc z;+Sqq#4()a;WtMGo|{REhEd_#%}BAH_O7L{0PV;x=O#3}g7AFN`>fuuC9ozH%ml{o zb-XMU-kjo4pD)u!0a+a9vn6sAug>sWs{~%B7LlVYf^+{3;@5}e>@DIhjSNwWJ2(%Ae}j8V=Jl{X|s8;~#|wA6zp zoi6sqi{2waIyvTT3SVC0%tW)zQP~uyub7RG6-G5jUGY zx78@>acFz6r1RJ7VfHcZaC+Lm=SnWZfitw4-%yFQ zw(L)yTayeYnctwEPgA2W{0j9<("updateBaselines") { - description = "Update visual test baselines" - group = "verification" - - useJUnitPlatform { - includeTags("baseline") - } - - systemProperty("update.baselines", "true") - maxParallelForks = 1 - - testLogging { - events("passed", "skipped", "failed") - showStandardStreams = true - } -} - -tasks.register("testShapes") { - description = "Run shape-related visual tests" - group = "verification" - - useJUnitPlatform { - includeTags("shapes") - } - - maxParallelForks = 1 -} - -tasks.register("testBasicShapes") { - description = "Run basic shapes visual tests" - group = "verification" - - useJUnitPlatform { - includeTags("basic") - } - - outputs.upToDateWhen { false } - maxParallelForks = 1 - - // Add test logging to see what's happening - testLogging { - events("passed", "skipped", "failed", "started") - showStandardStreams = true - exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL - } -} - -// Task to run ONLY visual tests (no other test types) -tasks.register("visualTests") { - description = "Run all visual tests" - group = "verification" - - useJUnitPlatform { - // Include all tests in the visual test package - includeEngines("junit-jupiter") - } - - filter { - includeTestsMatching("visual.*") - } - - outputs.upToDateWhen { false } - maxParallelForks = 1 - - testLogging { - events("passed", "skipped", "failed", "started") - showStandardStreams = true - displayGranularity = 2 - } -} - -tasks.register("testRendering") { - description = "Run rendering visual tests" - group = "verification" - - useJUnitPlatform { - includeTags("rendering") - } - - outputs.upToDateWhen { false } - maxParallelForks = 1 - - testLogging { - events("passed", "skipped", "failed", "started") - showStandardStreams = true - } -} - -tasks.register("runSuite") { - description = "Run specific test suite (use -PsuiteClass=SuiteName)" - group = "verification" - - useJUnitPlatform { - val suiteClass = project.findProperty("suiteClass") as String? - ?: "visual.suites.AllVisualTests" - includeTags(suiteClass) - } - - outputs.upToDateWhen { false } - maxParallelForks = 1 -} - -// Update baselines for specific suite -tasks.register("updateBaselinesForSuite") { - description = "Update baselines for specific suite (use -Psuite=tag)" - group = "verification" - - useJUnitPlatform { - val suite = project.findProperty("suite") as String? ?: "baseline" - includeTags(suite, "baseline") - } - - systemProperty("update.baselines", "true") - outputs.upToDateWhen { false } - maxParallelForks = 1 -} - -// CI-specific test task -tasks.register("ciTest") { - description = "Run visual tests in CI mode" - group = "verification" - - useJUnitPlatform() - - outputs.upToDateWhen { false } - - maxParallelForks = 1 - - testLogging { - events("passed", "skipped", "failed") - showStandardStreams = true - //exceptionFormat = org.gradle.api.tasks.testing.TestExceptionFormat.FULL - } - - // Generate XML reports for CI - reports { - junitXml.required.set(true) - html.required.set(true) - } - - // Fail fast in CI - failFast = true -} - -// Clean task for visual test artifacts -tasks.register("cleanVisualTestFiles") { - description = "Clean visual test artifacts" - group = "build" - - delete(fileTree(".") { - include("diff_*.png") - }) - - // Don't delete baselines by default - be explicit - doLast { - println("✓ Cleaned diff images") - println(" Baselines preserved in __screenshots__/") - println(" To clean baselines: rm -rf __screenshots__/") - } -} - -// Separate task to clean everything including baselines (dangerous!) -tasks.register("cleanAllVisualTestFiles") { - description = "Clean ALL visual test files INCLUDING BASELINES (use with caution!)" - group = "build" - - delete(fileTree(".") { - include("__screenshots__/**") - include("diff_*.png") - }) - - doFirst { - println("⚠️ WARNING: This will delete all baseline images!") - } -} - -tasks.named("clean") { - dependsOn("cleanVisualTestFiles") -} \ No newline at end of file diff --git a/visual-tests/src/main/java/processing/test/visual/BaselineManager.java b/visual-tests/src/main/java/processing/test/visual/BaselineManager.java deleted file mode 100644 index a9d1186dc3..0000000000 --- a/visual-tests/src/main/java/processing/test/visual/BaselineManager.java +++ /dev/null @@ -1,54 +0,0 @@ -package processing.test.visual; - -import processing.core.PImage; - -import java.util.List; - -// Baseline manager for updating reference images -public class BaselineManager { - private VisualTestRunner tester; - - public BaselineManager(VisualTestRunner tester) { - this.tester = tester; - } - - public void updateBaseline(String testName, ProcessingSketch sketch) { - updateBaseline(testName, sketch, new TestConfig()); - } - - public void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { - System.out.println("Updating baseline for: " + testName); - - // Capture new image - SketchRunner runner = new SketchRunner(sketch, config); - runner.run(); - PImage newImage = runner.getImage(); - - // Save as baseline - String baselinePath = "__screenshots__/" + - testName.replaceAll("[^a-zA-Z0-9-_]", "-") + - "-" + detectPlatform() + ".png"; - newImage.save(baselinePath); - - System.out.println("Baseline updated: " + baselinePath); - } - - public void updateAllBaselines(ProcessingTestSuite suite) { - System.out.println("Updating all baselines..."); - List testNames = suite.getTestNames(); - - for (String testName : testNames) { - // Re-run the test to get the sketch and config - TestResult result = suite.runTest(testName); - // Note: In a real implementation, you'd need to store the sketch reference - // This is a simplified version - } - } - - private String detectPlatform() { - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("mac")) return "darwin"; - if (os.contains("win")) return "win32"; - return "linux"; - } -} diff --git a/visual-tests/src/main/java/processing/test/visual/ImageComparator.java b/visual-tests/src/main/java/processing/test/visual/ImageComparator.java deleted file mode 100644 index 66bb34b504..0000000000 --- a/visual-tests/src/main/java/processing/test/visual/ImageComparator.java +++ /dev/null @@ -1,417 +0,0 @@ -package processing.test.visual; - -import processing.core.*; -import java.util.*; - -class ComparisonResult { - public boolean passed; - public double mismatchRatio; - public boolean isFirstRun; - public PImage diffImage; - public ComparisonDetails details; - - public ComparisonResult(boolean passed, double mismatchRatio) { - this.passed = passed; - this.mismatchRatio = mismatchRatio; - this.isFirstRun = false; - } - - public ComparisonResult(boolean passed, PImage diffImage, ComparisonDetails details) { - this.passed = passed; - this.diffImage = diffImage; - this.details = details; - this.mismatchRatio = details != null ? (double) details.significantDiffPixels / (diffImage.width * diffImage.height) : 0.0; - this.isFirstRun = false; - } - - public static ComparisonResult createFirstRun() { - ComparisonResult result = new ComparisonResult(false, 0.0); - result.isFirstRun = true; - return result; - } - - public void saveDiffImage(String filePath) { - if (diffImage != null) { - diffImage.save(filePath); - System.out.println("Diff image saved: " + filePath); - } - } -} - -class ComparisonDetails { - public int totalDiffPixels; - public int significantDiffPixels; - public List clusters; - - public ComparisonDetails(int totalDiffPixels, int significantDiffPixels, List clusters) { - this.totalDiffPixels = totalDiffPixels; - this.significantDiffPixels = significantDiffPixels; - this.clusters = clusters; - } - - public void printDetails() { - System.out.println(" Total diff pixels: " + totalDiffPixels); - System.out.println(" Significant diff pixels: " + significantDiffPixels); - System.out.println(" Clusters found: " + clusters.size()); - - long lineShiftClusters = clusters.stream().filter(c -> c.isLineShift).count(); - if (lineShiftClusters > 0) { - System.out.println(" Line shift clusters (ignored): " + lineShiftClusters); - } - - // Print cluster details - for (int i = 0; i < clusters.size(); i++) { - ClusterInfo cluster = clusters.get(i); - System.out.println(" Cluster " + (i+1) + ": size=" + cluster.size + - ", lineShift=" + cluster.isLineShift); - } - } -} - -// Individual cluster information -class ClusterInfo { - public int size; - public List pixels; - public boolean isLineShift; - - public ClusterInfo(int size, List pixels, boolean isLineShift) { - this.size = size; - this.pixels = pixels; - this.isLineShift = isLineShift; - } -} - -// Simple 2D point -class Point2D { - public int x, y; - - public Point2D(int x, int y) { - this.x = x; - this.y = y; - } -} - -// Interface for pixel matching algorithms -interface PixelMatchingAlgorithm { - ComparisonResult compare(PImage baseline, PImage actual, double threshold); -} - -// Your sophisticated pixel matching algorithm -public class ImageComparator implements PixelMatchingAlgorithm { - - // Algorithm constants - private static final int MAX_SIDE = 400; - private static final int BG_COLOR = 0xFFFFFFFF; // White background - private static final int MIN_CLUSTER_SIZE = 4; - private static final int MAX_TOTAL_DIFF_PIXELS = 40; - private static final double DEFAULT_THRESHOLD = 0.5; - private static final double ALPHA = 0.1; - - private PApplet p; // Reference to PApplet for PImage creation - - public ImageComparator(PApplet p) { - this.p = p; - } - - @Override - public ComparisonResult compare(PImage baseline, PImage actual, double threshold) { - if (baseline == null || actual == null) { - return new ComparisonResult(false, 1.0); - } - - try { - return performComparison(baseline, actual, threshold); - } catch (Exception e) { - System.err.println("Comparison failed: " + e.getMessage()); - return new ComparisonResult(false, 1.0); - } - } - - private ComparisonResult performComparison(PImage baseline, PImage actual, double threshold) { - // Calculate scaling - double scale = Math.min( - (double) MAX_SIDE / baseline.width, - (double) MAX_SIDE / baseline.height - ); - - double ratio = (double) baseline.width / baseline.height; - boolean narrow = ratio != 1.0; - if (narrow) { - scale *= 2; - } - - // Resize images - PImage scaledActual = resizeImage(actual, scale); - PImage scaledBaseline = resizeImage(baseline, scale); - - // Ensure both images have the same dimensions - int width = scaledBaseline.width; - int height = scaledBaseline.height; - - // Create canvases with background color - PImage actualCanvas = createCanvasWithBackground(scaledActual, width, height); - PImage baselineCanvas = createCanvasWithBackground(scaledBaseline, width, height); - - // Create diff output canvas - PImage diffCanvas = p.createImage(width, height, PImage.RGB); - - // Run pixelmatch equivalent - int diffCount = pixelmatch(actualCanvas, baselineCanvas, diffCanvas, width, height, DEFAULT_THRESHOLD); - - // If no differences, return early - if (diffCount == 0) { - return new ComparisonResult(true, diffCanvas, null); - } - - // Post-process to identify and filter out isolated differences - Set visited = new HashSet<>(); - List clusterSizes = new ArrayList<>(); - - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int pos = y * width + x; - - // If this is a diff pixel and not yet visited - if (isDiffPixel(diffCanvas, x, y) && !visited.contains(pos)) { - ClusterInfo clusterInfo = findClusterSize(diffCanvas, x, y, width, height, visited); - clusterSizes.add(clusterInfo); - } - } - } - - // Determine if the differences are significant - List nonLineShiftClusters = clusterSizes.stream() - .filter(cluster -> !cluster.isLineShift && cluster.size >= MIN_CLUSTER_SIZE) - .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); - - // Calculate significant differences excluding line shifts - int significantDiffPixels = nonLineShiftClusters.stream() - .mapToInt(cluster -> cluster.size) - .sum(); - - // Determine test result - boolean passed = diffCount == 0 || - significantDiffPixels == 0 || - (significantDiffPixels <= MAX_TOTAL_DIFF_PIXELS && nonLineShiftClusters.size() <= 2); - - ComparisonDetails details = new ComparisonDetails(diffCount, significantDiffPixels, clusterSizes); - - return new ComparisonResult(passed, diffCanvas, details); - } - - private PImage resizeImage(PImage image, double scale) { - int newWidth = (int) Math.ceil(image.width * scale); - int newHeight = (int) Math.ceil(image.height * scale); - - PImage resized = p.createImage(newWidth, newHeight, PImage.RGB); - resized.copy(image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight); - - return resized; - } - - private PImage createCanvasWithBackground(PImage image, int width, int height) { - PImage canvas = p.createImage(width, height, PImage.RGB); - - // Fill with background color (white) - canvas.loadPixels(); - for (int i = 0; i < canvas.pixels.length; i++) { - canvas.pixels[i] = BG_COLOR; - } - canvas.updatePixels(); - - // Draw the image on top - canvas.copy(image, 0, 0, image.width, image.height, 0, 0, image.width, image.height); - - return canvas; - } - - private int pixelmatch(PImage actual, PImage expected, PImage diff, int width, int height, double threshold) { - int diffCount = 0; - - actual.loadPixels(); - expected.loadPixels(); - diff.loadPixels(); - - for (int i = 0; i < actual.pixels.length; i++) { - int actualColor = actual.pixels[i]; - int expectedColor = expected.pixels[i]; - - double delta = colorDelta(actualColor, expectedColor); - - if (delta > threshold) { - // Mark as different (bright red pixel) - diff.pixels[i] = 0xFFFF0000; // Red - diffCount++; - } else { - // Mark as same (dimmed version of actual image) - int dimColor = dimColor(actualColor, ALPHA); - diff.pixels[i] = dimColor; - } - } - - diff.updatePixels(); - return diffCount; - } - - private double colorDelta(int color1, int color2) { - int r1 = (color1 >> 16) & 0xFF; - int g1 = (color1 >> 8) & 0xFF; - int b1 = color1 & 0xFF; - int a1 = (color1 >> 24) & 0xFF; - - int r2 = (color2 >> 16) & 0xFF; - int g2 = (color2 >> 8) & 0xFF; - int b2 = color2 & 0xFF; - int a2 = (color2 >> 24) & 0xFF; - - int dr = r1 - r2; - int dg = g1 - g2; - int db = b1 - b2; - int da = a1 - a2; - - return Math.sqrt(dr * dr + dg * dg + db * db + da * da) / 255.0; - } - - private int dimColor(int color, double alpha) { - int r = (int) (((color >> 16) & 0xFF) * alpha); - int g = (int) (((color >> 8) & 0xFF) * alpha); - int b = (int) ((color & 0xFF) * alpha); - int a = (int) (255 * alpha); - - r = Math.max(0, Math.min(255, r)); - g = Math.max(0, Math.min(255, g)); - b = Math.max(0, Math.min(255, b)); - a = Math.max(0, Math.min(255, a)); - - return (a << 24) | (r << 16) | (g << 8) | b; - } - - private boolean isDiffPixel(PImage image, int x, int y) { - if (x < 0 || x >= image.width || y < 0 || y >= image.height) return false; - - image.loadPixels(); - int color = image.pixels[y * image.width + x]; - - int r = (color >> 16) & 0xFF; - int g = (color >> 8) & 0xFF; - int b = color & 0xFF; - - return r == 255 && g == 0 && b == 0; - } - - private ClusterInfo findClusterSize(PImage diffImage, int startX, int startY, int width, int height, Set visited) { - List queue = new ArrayList<>(); - queue.add(new Point2D(startX, startY)); - - int size = 0; - List clusterPixels = new ArrayList<>(); - - while (!queue.isEmpty()) { - Point2D point = queue.remove(0); - int pos = point.y * width + point.x; - - // Skip if already visited - if (visited.contains(pos)) continue; - - // Skip if not a diff pixel - if (!isDiffPixel(diffImage, point.x, point.y)) continue; - - // Mark as visited - visited.add(pos); - size++; - clusterPixels.add(point); - - // Add neighbors to queue - for (int dy = -1; dy <= 1; dy++) { - for (int dx = -1; dx <= 1; dx++) { - if (dx == 0 && dy == 0) continue; - - int nx = point.x + dx; - int ny = point.y + dy; - - // Skip if out of bounds - if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; - - // Skip if already visited - int npos = ny * width + nx; - if (!visited.contains(npos)) { - queue.add(new Point2D(nx, ny)); - } - } - } - } - - // Determine if this is a line shift - boolean isLineShift = detectLineShift(clusterPixels, diffImage, width, height); - - return new ClusterInfo(size, clusterPixels, isLineShift); - } - - private boolean detectLineShift(List clusterPixels, PImage diffImage, int width, int height) { - if (clusterPixels.isEmpty()) return false; - - int linelikePixels = 0; - - for (Point2D pixel : clusterPixels) { - int neighbors = 0; - for (int dy = -1; dy <= 1; dy++) { - for (int dx = -1; dx <= 1; dx++) { - if (dx == 0 && dy == 0) continue; // Skip self - - int nx = pixel.x + dx; - int ny = pixel.y + dy; - - // Skip if out of bounds - if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; - - // Check if neighbor is a diff pixel - if (isDiffPixel(diffImage, nx, ny)) { - neighbors++; - } - } - } - - // Line-like pixels typically have 1-2 neighbors - if (neighbors <= 2) { - linelikePixels++; - } - } - - // If most pixels (>80%) in the cluster have ≤2 neighbors, it's likely a line shift - return (double) linelikePixels / clusterPixels.size() > 0.8; - } - - // Configuration methods - public ImageComparator setMaxSide(int maxSide) { - // For future configurability - return this; - } - - public ImageComparator setMinClusterSize(int minClusterSize) { - // For future configurability - return this; - } - - public ImageComparator setMaxTotalDiffPixels(int maxTotalDiffPixels) { - // For future configurability - return this; - } -} - -// Utility class for algorithm configuration -class ComparatorConfig { - public int maxSide = 400; - public int minClusterSize = 4; - public int maxTotalDiffPixels = 40; - public double threshold = 0.5; - public double alpha = 0.1; - public int backgroundColor = 0xFFFFFFFF; - - public ComparatorConfig() {} - - public ComparatorConfig(int maxSide, int minClusterSize, int maxTotalDiffPixels) { - this.maxSide = maxSide; - this.minClusterSize = minClusterSize; - this.maxTotalDiffPixels = maxTotalDiffPixels; - } -} \ No newline at end of file diff --git a/visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java b/visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java deleted file mode 100644 index c879ffbb1e..0000000000 --- a/visual-tests/src/main/java/processing/test/visual/ProcessingSketch.java +++ /dev/null @@ -1,9 +0,0 @@ -package processing.test.visual; - -import processing.core.PApplet; - -// Interface for user sketches -public interface ProcessingSketch { - void setup(PApplet p); - void draw(PApplet p); -} diff --git a/visual-tests/src/main/java/processing/test/visual/TestConfig.java b/visual-tests/src/main/java/processing/test/visual/TestConfig.java deleted file mode 100644 index ff69b2e75f..0000000000 --- a/visual-tests/src/main/java/processing/test/visual/TestConfig.java +++ /dev/null @@ -1,33 +0,0 @@ -package processing.test.visual; - -// Test configuration class -public class TestConfig { - public int width = 800; - public int height = 600; - public int[] backgroundColor = {255, 255, 255}; // RGB - public long renderWaitTime = 100; // milliseconds - public double threshold = 0.1; - - public TestConfig() {} - - public TestConfig(int width, int height) { - this.width = width; - this.height = height; - } - - public TestConfig(int width, int height, int[] backgroundColor) { - this.width = width; - this.height = height; - this.backgroundColor = backgroundColor; - } - - public TestConfig setThreshold(double threshold) { - this.threshold = threshold; - return this; - } - - public TestConfig setRenderWaitTime(long waitTime) { - this.renderWaitTime = waitTime; - return this; - } -} diff --git a/visual-tests/src/main/java/processing/test/visual/TestResult.java b/visual-tests/src/main/java/processing/test/visual/TestResult.java deleted file mode 100644 index 139e57561f..0000000000 --- a/visual-tests/src/main/java/processing/test/visual/TestResult.java +++ /dev/null @@ -1,45 +0,0 @@ -package processing.test.visual; - -// Enhanced test result with detailed information -public class TestResult { - public String testName; - public boolean passed; - public double mismatchRatio; - public String error; - public boolean isFirstRun; - public ComparisonDetails details; - - public TestResult(String testName, ComparisonResult comparison) { - this.testName = testName; - this.passed = comparison.passed; - this.mismatchRatio = comparison.mismatchRatio; - this.isFirstRun = comparison.isFirstRun; - this.details = comparison.details; - } - - public static TestResult createError(String testName, String error) { - TestResult result = new TestResult(); - result.testName = testName; - result.passed = false; - result.error = error; - return result; - } - - private TestResult() {} // For error constructor - - public void printResult() { - System.out.print(testName + ": "); - if (error != null) { - System.out.println("ERROR - " + error); - } else if (isFirstRun) { - System.out.println("BASELINE CREATED"); - } else if (passed) { - System.out.println("PASSED"); - } else { - System.out.println("FAILED (mismatch: " + String.format("%.4f", mismatchRatio * 100) + "%)"); - if (details != null) { - details.printDetails(); - } - } - } -} diff --git a/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java b/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java deleted file mode 100644 index 0f0ffe1003..0000000000 --- a/visual-tests/src/main/java/processing/test/visual/VisualTestRunner.java +++ /dev/null @@ -1,321 +0,0 @@ -package processing.test.visual; - -import processing.core.*; -import java.io.*; -import java.nio.file.*; -import java.util.*; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; - -// Core visual tester class -public class VisualTestRunner { - - private String screenshotDir; - private PixelMatchingAlgorithm pixelMatcher; - private String platform; - - public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher) { - this.pixelMatcher = pixelMatcher; - this.screenshotDir = "__screenshots__"; - this.platform = detectPlatform(); - createDirectoryIfNotExists(screenshotDir); - } - - public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher, String screenshotDir) { - this.pixelMatcher = pixelMatcher; - this.screenshotDir = screenshotDir; - this.platform = detectPlatform(); - createDirectoryIfNotExists(screenshotDir); - } - - // Main test execution method - public TestResult runVisualTest(String testName, ProcessingSketch sketch) { - return runVisualTest(testName, sketch, new TestConfig()); - } - - public TestResult runVisualTest(String testName, ProcessingSketch sketch, TestConfig config) { - try { - System.out.println("Running visual test: " + testName); - - // Capture screenshot from sketch - PImage actualImage = captureSketch(sketch, config); - - // Compare with baseline - ComparisonResult comparison = compareWithBaseline(testName, actualImage, config); - - return new TestResult(testName, comparison); - - } catch (Exception e) { - return TestResult.createError(testName, e.getMessage()); - } - } - - // Capture PImage from Processing sketch - private PImage captureSketch(ProcessingSketch sketch, TestConfig config) { - SketchRunner runner = new SketchRunner(sketch, config); - runner.run(); - return runner.getImage(); - } - - // Compare actual image with baseline - private ComparisonResult compareWithBaseline(String testName, PImage actualImage, TestConfig config) { - String baselinePath = getBaselinePath(testName); - - PImage baselineImage = loadBaseline(baselinePath); - - if (baselineImage == null) { - // First run - save as baseline - saveBaseline(testName, actualImage); - return ComparisonResult.createFirstRun(); - } - - // Use your sophisticated pixel matching algorithm - ComparisonResult result = pixelMatcher.compare(baselineImage, actualImage, config.threshold); - - // Save diff images if test failed - if (!result.passed && result.diffImage != null) { - saveDiffImage(testName, result.diffImage); - } - - return result; - } - - // Save diff image for debugging - private void saveDiffImage(String testName, PImage diffImage) { - String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_]", "-"); - String diffPath; - if (sanitizedName.contains("/")) { - diffPath = "diff_" + sanitizedName.replace("/", "_") + "-" + platform + ".png"; - } else { - diffPath = "diff_" + sanitizedName + "-" + platform + ".png"; - } - - File diffFile = new File(diffPath); - diffFile.getParentFile().mkdirs(); - - diffImage.save(diffPath); - System.out.println("Diff image saved: " + diffPath); - } - - // Utility methods - private String detectPlatform() { - String os = System.getProperty("os.name").toLowerCase(); - if (os.contains("mac")) return "darwin"; - if (os.contains("win")) return "win32"; - return "linux"; - } - - private void createDirectoryIfNotExists(String dir) { - try { - Files.createDirectories(Paths.get(dir)); - } catch (IOException e) { - System.err.println("Failed to create directory: " + dir); - } - } - - private String getBaselinePath(String testName) { - String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_/]", "-"); - - return screenshotDir + "/" + sanitizedName + "-" + platform + ".png"; - } - - // Replace loadBaseline method: - private PImage loadBaseline(String path) { - File file = new File(path); - if (!file.exists()) { - System.out.println("loadBaseline: File doesn't exist: " + file.getAbsolutePath()); - return null; - } - - try { - System.out.println("loadBaseline: Loading from " + file.getAbsolutePath()); - - // Use Java ImageIO instead of PApplet - BufferedImage img = ImageIO.read(file); - - if (img == null) { - System.out.println("loadBaseline: ImageIO returned null"); - return null; - } - - // Convert BufferedImage to PImage - PImage pImg = new PImage(img.getWidth(), img.getHeight(), PImage.RGB); - img.getRGB(0, 0, pImg.width, pImg.height, pImg.pixels, 0, pImg.width); - pImg.updatePixels(); - - System.out.println("loadBaseline: ✓ Loaded " + pImg.width + "x" + pImg.height); - return pImg; - - } catch (Exception e) { - System.err.println("loadBaseline: Error loading image: " + e.getMessage()); - e.printStackTrace(); - return null; - } - } - - // Replace saveBaseline method: - private void saveBaseline(String testName, PImage image) { - String path = getBaselinePath(testName); - - if (image == null) { - System.out.println("saveBaseline: ✗ Image is null!"); - return; - } - - try { - // Convert PImage to BufferedImage - BufferedImage bImg = new BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB); - image.loadPixels(); - bImg.setRGB(0, 0, image.width, image.height, image.pixels, 0, image.width); - - // Create File object and ensure parent directories exist - File outputFile = new File(path); - outputFile.getParentFile().mkdirs(); // This creates nested directories - - // Use Java ImageIO to save - ImageIO.write(bImg, "PNG", outputFile); - - System.out.println("Baseline saved: " + path); - - } catch (Exception e) { - System.err.println("Failed to save baseline: " + path); - e.printStackTrace(); - } - } -} -class SketchRunner extends PApplet { - - private ProcessingSketch userSketch; - private TestConfig config; - private PImage capturedImage; - private volatile boolean rendered = false; - - public SketchRunner(ProcessingSketch userSketch, TestConfig config) { - this.userSketch = userSketch; - this.config = config; - } - - public void settings() { - size(config.width, config.height); - } - - public void setup() { - noLoop(); - - // Set background if specified - if (config.backgroundColor != null) { - background(config.backgroundColor[0], config.backgroundColor[1], config.backgroundColor[2]); - } - - // Call user setup - userSketch.setup(this); - } - - public void draw() { - if (!rendered) { - userSketch.draw(this); - capturedImage = get(); - rendered = true; - noLoop(); - } - } - - public void run() { - String[] args = {"SketchRunner"}; - PApplet.runSketch(args, this); - - // Simple polling with timeout - int maxWait = 100; // 10 seconds max - int waited = 0; - - while (!rendered && waited < maxWait) { - try { - Thread.sleep(100); - waited++; - - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } - } - - // Additional wait time - try { - Thread.sleep(config.renderWaitTime); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - if (surface != null) { - surface.setVisible(false); - } - try { - Thread.sleep(200); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - - public PImage getImage() { - return capturedImage; - } -} - -// Test suite for organizing multiple tests -class ProcessingTestSuite { - private VisualTestRunner tester; - private List tests; - - public ProcessingTestSuite(VisualTestRunner tester) { - this.tester = tester; - this.tests = new ArrayList<>(); - } - - public void addTest(String name, ProcessingSketch sketch) { - addTest(name, sketch, new TestConfig()); - } - - public void addTest(String name, ProcessingSketch sketch, TestConfig config) { - tests.add(new VisualTest(name, sketch, config)); - } - - public List runAll() { - System.out.println("Running " + tests.size() + " visual tests..."); - List results = new ArrayList<>(); - - for (VisualTest test : tests) { - TestResult result = tester.runVisualTest(test.name, test.sketch, test.config); - result.printResult(); - results.add(result); - } - - return results; - } - - public TestResult runTest(String testName) { - VisualTest test = tests.stream() - .filter(t -> t.name.equals(testName)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("Test not found: " + testName)); - - return tester.runVisualTest(test.name, test.sketch, test.config); - } - - public List getTestNames() { - return tests.stream().map(t -> t.name).collect(ArrayList::new, ArrayList::add, ArrayList::addAll); - } - - // Helper class for internal test storage - private static class VisualTest { - String name; - ProcessingSketch sketch; - TestConfig config; - - VisualTest(String name, ProcessingSketch sketch, TestConfig config) { - this.name = name; - this.sketch = sketch; - this.config = config; - } - } -} diff --git a/visual-tests/src/test/java/visual/base/VisualTest.java b/visual-tests/src/test/java/visual/base/VisualTest.java deleted file mode 100644 index e574a5b6f7..0000000000 --- a/visual-tests/src/test/java/visual/base/VisualTest.java +++ /dev/null @@ -1,61 +0,0 @@ -package visual.base; - -import org.junit.jupiter.api.*; -import processing.core.*; -import static org.junit.jupiter.api.Assertions.*; -import processing.test.visual.*; -import java.nio.file.*; -import java.io.File; - -/** - * Base class for Processing visual tests using JUnit 5 - */ -public abstract class VisualTest { - - protected static VisualTestRunner testRunner; - protected static ImageComparator comparator; - - @BeforeAll - public static void setupTestRunner() { - PApplet tempApplet = new PApplet(); - comparator = new ImageComparator(tempApplet); - testRunner = new VisualTestRunner(comparator); - - System.out.println("Visual test runner initialized"); - } - - /** - * Helper method to run a visual test - */ - protected void assertVisualMatch(String testName, ProcessingSketch sketch) { - assertVisualMatch(testName, sketch, new TestConfig()); - } - - protected void assertVisualMatch(String testName, ProcessingSketch sketch, TestConfig config) { - TestResult result = testRunner.runVisualTest(testName, sketch, config); - - // Print result for debugging - result.printResult(); - - // Handle different result types - if (result.isFirstRun) { - // First run - baseline created, mark as skipped - Assumptions.assumeTrue(false, "Baseline created for " + testName + ". Run tests again to verify."); - } else if (result.error != null) { - fail("Test error: " + result.error); - } else { - // Assert that the test passed - Assertions.assertTrue(result.passed, - String.format("Visual test '%s' failed with mismatch ratio: %.4f%%", - testName, result.mismatchRatio * 100)); - } - } - - /** - * Update baseline for a specific test (useful for maintenance) - */ - protected void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { - BaselineManager manager = new BaselineManager(testRunner); - manager.updateBaseline(testName, sketch, config); - } -} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/rendering/GradientTest.java b/visual-tests/src/test/java/visual/rendering/GradientTest.java deleted file mode 100644 index 479d0872b1..0000000000 --- a/visual-tests/src/test/java/visual/rendering/GradientTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package visual.rendering; - -import org.junit.jupiter.api.*; -import processing.core.*; -import visual.base.VisualTest; -import processing.test.visual.ProcessingSketch; -import processing.test.visual.TestConfig; - -@Tag("rendering") -@Tag("gradients") -public class GradientTest extends VisualTest { - - @Test - @DisplayName("Linear gradient renders correctly") - public void testLinearGradient() { - TestConfig config = new TestConfig(600, 400); - - assertVisualMatch("rendering/linear-gradient", new ProcessingSketch() { - @Override - public void setup(PApplet p) { - p.noStroke(); - } - - @Override - public void draw(PApplet p) { - for (int y = 0; y < p.height; y++) { - float inter = PApplet.map(y, 0, p.height, 0, 1); - int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); - p.stroke(c); - p.line(0, y, p.width, y); - } - } - }, config); - } -} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/shapes/BasicShapeTest.java b/visual-tests/src/test/java/visual/shapes/BasicShapeTest.java deleted file mode 100644 index 6756b2557e..0000000000 --- a/visual-tests/src/test/java/visual/shapes/BasicShapeTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package visual.shapes; - - -import org.junit.jupiter.api.*; -import processing.core.*; -import visual.base.*; -import processing.test.visual.*; - -@Tag("basic") -@Tag("shapes") -@TestMethodOrder(MethodOrderer.OrderAnnotation.class) -public class BasicShapeTest extends VisualTest { - - @Test - @Order(1) - @DisplayName("Red circle renders correctly") - public void testRedCircle() { - assertVisualMatch("basic-shapes/red-circle", new ProcessingSketch() { - @Override - public void setup(PApplet p) { - p.noStroke(); - } - - @Override - public void draw(PApplet p) { - p.background(255, 255, 255); - p.fill(255, 0, 0); - p.ellipse(p.width/2, p.height/2, 100, 100); - } - }); - } - - @Test - @Order(2) - @DisplayName("Blue square renders correctly") - public void testBlueSquare() { - assertVisualMatch("basic-shapes/blue-square", new ProcessingSketch() { - @Override - public void setup(PApplet p) { - p.stroke(0); - p.strokeWeight(2); - } - - @Override - public void draw(PApplet p) { - p.background(255); - p.fill(0, 0, 255); - p.rect(p.width/2 - 50, p.height/2 - 50, 100, 100); - } - }); - } - - @Test - @Order(3) - @DisplayName("Green circle renders correctly") - public void testGreenCircle() { - assertVisualMatch("basic-shapes/green-circle", new ProcessingSketch() { - @Override - public void setup(PApplet p) { - p.noStroke(); - } - - @Override - public void draw(PApplet p) { - p.background(255, 255, 255); - p.fill(0, 255, 0); - p.ellipse(p.width/2, p.height/2, 100, 100); - } - }); - } - - @Test - @Order(4) - @DisplayName("Custom size canvas") - public void testCustomSize() { - TestConfig config = new TestConfig(600, 400); - - assertVisualMatch("basic-shapes/custom-size-rect", new ProcessingSketch() { - @Override - public void setup(PApplet p) { - p.noStroke(); - } - - @Override - public void draw(PApplet p) { - p.background(240, 240, 240); - p.fill(128, 0, 128); - p.rect(50, 50, p.width - 100, p.height - 100); - } - }, config); - } -} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/suites/BasicShapesSuite.java b/visual-tests/src/test/java/visual/suites/BasicShapesSuite.java deleted file mode 100644 index 7145e04156..0000000000 --- a/visual-tests/src/test/java/visual/suites/BasicShapesSuite.java +++ /dev/null @@ -1,11 +0,0 @@ -package visual.suites; - -import org.junit.platform.suite.api.*; - -@Suite -@SuiteDisplayName("Basic Shapes Visual Tests") -@SelectPackages("visual.shapes") -@IncludeTags("basic") -public class BasicShapesSuite { - // Empty class - just holds annotations -} \ No newline at end of file diff --git a/visual-tests/src/test/java/visual/suites/RenderingSuite.java b/visual-tests/src/test/java/visual/suites/RenderingSuite.java deleted file mode 100644 index 6637cd06dc..0000000000 --- a/visual-tests/src/test/java/visual/suites/RenderingSuite.java +++ /dev/null @@ -1,11 +0,0 @@ -package visual.suites; - -import org.junit.platform.suite.api.*; - -@Suite -@SuiteDisplayName("Rendering Tests") -@SelectPackages("processing.test.visual.rendering") -@IncludeTags("rendering") -public class RenderingSuite { - // Empty class - just holds annotations -} From bf211316ed9db7341c07a5f0bf2513f7381c313f Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 16 Oct 2025 16:52:31 +0530 Subject: [PATCH 19/29] updated the core/gradle file --- core/build.gradle.kts | 44 ++++++++++++++++++++++++++++--------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8f7211b131..14f28236cc 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -11,18 +11,18 @@ repositories { maven { url = uri("https://jogamp.org/deployment/maven") } } -sourceSets{ - main{ - java{ +sourceSets { + main { + java { srcDirs("src") } - resources{ + resources { srcDirs("src") exclude("**/*.java") } } - test{ - java{ + test { + java { srcDirs("test") } } @@ -33,13 +33,31 @@ dependencies { implementation(libs.gluegen) testImplementation(libs.junit) + testImplementation(libs.junitJupiter) + testImplementation(libs.junitJupiterParams) + testImplementation(libs.junitPlatformSuite) + testImplementation(libs.assertjCore) } -mavenPublishing{ +// Simple JUnit 5 configuration - let JUnit handle everything +tasks.test { + useJUnitPlatform() // JUnit discovers and runs all tests + + // Only configuration, not orchestration + outputs.upToDateWhen { false } + maxParallelForks = 1 + + testLogging { + events("passed", "skipped", "failed", "started") + showStandardStreams = true + } +} + +mavenPublishing { publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL) signAllPublications() - pom{ + pom { name.set("Processing Core") description.set("Processing Core") url.set("https://processing.org") @@ -59,7 +77,7 @@ mavenPublishing{ name.set("Ben Fry") } } - scm{ + scm { url.set("https://github.com/processing/processing4") connection.set("scm:git:git://github.com/processing/processing4.git") developerConnection.set("scm:git:ssh://git@github.com/processing/processing4.git") @@ -67,13 +85,9 @@ mavenPublishing{ } } - -tasks.test { - useJUnit() -} tasks.withType { duplicatesStrategy = DuplicatesStrategy.EXCLUDE } -tasks.compileJava{ +tasks.compileJava { options.encoding = "UTF-8" -} +} \ No newline at end of file From 86f88470aa6b5b71251f432ab9aebb993407a8b5 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 16 Oct 2025 16:55:12 +0530 Subject: [PATCH 20/29] added the infrastructure --- .../visual/src/core/BaselineManager.java | 38 ++ .../visual/src/core/ImageComparator.java | 417 ++++++++++++++++++ .../visual/src/core/ProcessingSketch.java | 9 + .../visual/src/core/TestConfig.java | 33 ++ .../visual/src/core/TestResult.java | 45 ++ .../visual/src/core/VisualTestRunner.java | 263 +++++++++++ 6 files changed, 805 insertions(+) create mode 100644 core/test/processing/visual/src/core/BaselineManager.java create mode 100644 core/test/processing/visual/src/core/ImageComparator.java create mode 100644 core/test/processing/visual/src/core/ProcessingSketch.java create mode 100644 core/test/processing/visual/src/core/TestConfig.java create mode 100644 core/test/processing/visual/src/core/TestResult.java create mode 100644 core/test/processing/visual/src/core/VisualTestRunner.java diff --git a/core/test/processing/visual/src/core/BaselineManager.java b/core/test/processing/visual/src/core/BaselineManager.java new file mode 100644 index 0000000000..93123ab13c --- /dev/null +++ b/core/test/processing/visual/src/core/BaselineManager.java @@ -0,0 +1,38 @@ +package processing.visual.src.core; + +import processing.core.PImage; + +import java.util.List; + +// Baseline manager for updating reference images +public class BaselineManager { + private VisualTestRunner tester; + + public BaselineManager(VisualTestRunner tester) { + this.tester = tester; + } + + public void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + System.out.println("Updating baseline for: " + testName); + + // Capture new image + SketchRunner runner = new SketchRunner(sketch, config); + runner.run(); + PImage newImage = runner.getImage(); + + // Save as baseline + String baselinePath = "__screenshots__/" + + testName.replaceAll("[^a-zA-Z0-9-_]", "-") + + "-" + detectPlatform() + ".png"; + newImage.save(baselinePath); + + System.out.println("Baseline updated: " + baselinePath); + } + + private String detectPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) return "darwin"; + if (os.contains("win")) return "win32"; + return "linux"; + } +} diff --git a/core/test/processing/visual/src/core/ImageComparator.java b/core/test/processing/visual/src/core/ImageComparator.java new file mode 100644 index 0000000000..2b368e8e4b --- /dev/null +++ b/core/test/processing/visual/src/core/ImageComparator.java @@ -0,0 +1,417 @@ +package processing.visual.src.core; + +import processing.core.*; +import java.util.*; + +class ComparisonResult { + public boolean passed; + public double mismatchRatio; + public boolean isFirstRun; + public PImage diffImage; + public ComparisonDetails details; + + public ComparisonResult(boolean passed, double mismatchRatio) { + this.passed = passed; + this.mismatchRatio = mismatchRatio; + this.isFirstRun = false; + } + + public ComparisonResult(boolean passed, PImage diffImage, ComparisonDetails details) { + this.passed = passed; + this.diffImage = diffImage; + this.details = details; + this.mismatchRatio = details != null ? (double) details.significantDiffPixels / (diffImage.width * diffImage.height) : 0.0; + this.isFirstRun = false; + } + + public static ComparisonResult createFirstRun() { + ComparisonResult result = new ComparisonResult(false, 0.0); + result.isFirstRun = true; + return result; + } + + public void saveDiffImage(String filePath) { + if (diffImage != null) { + diffImage.save(filePath); + System.out.println("Diff image saved: " + filePath); + } + } +} + +class ComparisonDetails { + public int totalDiffPixels; + public int significantDiffPixels; + public List clusters; + + public ComparisonDetails(int totalDiffPixels, int significantDiffPixels, List clusters) { + this.totalDiffPixels = totalDiffPixels; + this.significantDiffPixels = significantDiffPixels; + this.clusters = clusters; + } + + public void printDetails() { + System.out.println(" Total diff pixels: " + totalDiffPixels); + System.out.println(" Significant diff pixels: " + significantDiffPixels); + System.out.println(" Clusters found: " + clusters.size()); + + long lineShiftClusters = clusters.stream().filter(c -> c.isLineShift).count(); + if (lineShiftClusters > 0) { + System.out.println(" Line shift clusters (ignored): " + lineShiftClusters); + } + + // Print cluster details + for (int i = 0; i < clusters.size(); i++) { + ClusterInfo cluster = clusters.get(i); + System.out.println(" Cluster " + (i+1) + ": size=" + cluster.size + + ", lineShift=" + cluster.isLineShift); + } + } +} + +// Individual cluster information +class ClusterInfo { + public int size; + public List pixels; + public boolean isLineShift; + + public ClusterInfo(int size, List pixels, boolean isLineShift) { + this.size = size; + this.pixels = pixels; + this.isLineShift = isLineShift; + } +} + +// Simple 2D point +class Point2D { + public int x, y; + + public Point2D(int x, int y) { + this.x = x; + this.y = y; + } +} + +// Interface for pixel matching algorithms +interface PixelMatchingAlgorithm { + ComparisonResult compare(PImage baseline, PImage actual, double threshold); +} + +// Your sophisticated pixel matching algorithm +public class ImageComparator implements PixelMatchingAlgorithm { + + // Algorithm constants + private static final int MAX_SIDE = 400; + private static final int BG_COLOR = 0xFFFFFFFF; // White background + private static final int MIN_CLUSTER_SIZE = 4; + private static final int MAX_TOTAL_DIFF_PIXELS = 40; + private static final double DEFAULT_THRESHOLD = 0.5; + private static final double ALPHA = 0.1; + + private PApplet p; // Reference to PApplet for PImage creation + + public ImageComparator(PApplet p) { + this.p = p; + } + + @Override + public ComparisonResult compare(PImage baseline, PImage actual, double threshold) { + if (baseline == null || actual == null) { + return new ComparisonResult(false, 1.0); + } + + try { + return performComparison(baseline, actual, threshold); + } catch (Exception e) { + System.err.println("Comparison failed: " + e.getMessage()); + return new ComparisonResult(false, 1.0); + } + } + + private ComparisonResult performComparison(PImage baseline, PImage actual, double threshold) { + // Calculate scaling + double scale = Math.min( + (double) MAX_SIDE / baseline.width, + (double) MAX_SIDE / baseline.height + ); + + double ratio = (double) baseline.width / baseline.height; + boolean narrow = ratio != 1.0; + if (narrow) { + scale *= 2; + } + + // Resize images + PImage scaledActual = resizeImage(actual, scale); + PImage scaledBaseline = resizeImage(baseline, scale); + + // Ensure both images have the same dimensions + int width = scaledBaseline.width; + int height = scaledBaseline.height; + + // Create canvases with background color + PImage actualCanvas = createCanvasWithBackground(scaledActual, width, height); + PImage baselineCanvas = createCanvasWithBackground(scaledBaseline, width, height); + + // Create diff output canvas + PImage diffCanvas = p.createImage(width, height, PImage.RGB); + + // Run pixelmatch equivalent + int diffCount = pixelmatch(actualCanvas, baselineCanvas, diffCanvas, width, height, DEFAULT_THRESHOLD); + + // If no differences, return early + if (diffCount == 0) { + return new ComparisonResult(true, diffCanvas, null); + } + + // Post-process to identify and filter out isolated differences + Set visited = new HashSet<>(); + List clusterSizes = new ArrayList<>(); + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int pos = y * width + x; + + // If this is a diff pixel and not yet visited + if (isDiffPixel(diffCanvas, x, y) && !visited.contains(pos)) { + ClusterInfo clusterInfo = findClusterSize(diffCanvas, x, y, width, height, visited); + clusterSizes.add(clusterInfo); + } + } + } + + // Determine if the differences are significant + List nonLineShiftClusters = clusterSizes.stream() + .filter(cluster -> !cluster.isLineShift && cluster.size >= MIN_CLUSTER_SIZE) + .collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + + // Calculate significant differences excluding line shifts + int significantDiffPixels = nonLineShiftClusters.stream() + .mapToInt(cluster -> cluster.size) + .sum(); + + // Determine test result + boolean passed = diffCount == 0 || + significantDiffPixels == 0 || + (significantDiffPixels <= MAX_TOTAL_DIFF_PIXELS && nonLineShiftClusters.size() <= 2); + + ComparisonDetails details = new ComparisonDetails(diffCount, significantDiffPixels, clusterSizes); + + return new ComparisonResult(passed, diffCanvas, details); + } + + private PImage resizeImage(PImage image, double scale) { + int newWidth = (int) Math.ceil(image.width * scale); + int newHeight = (int) Math.ceil(image.height * scale); + + PImage resized = p.createImage(newWidth, newHeight, PImage.RGB); + resized.copy(image, 0, 0, image.width, image.height, 0, 0, newWidth, newHeight); + + return resized; + } + + private PImage createCanvasWithBackground(PImage image, int width, int height) { + PImage canvas = p.createImage(width, height, PImage.RGB); + + // Fill with background color (white) + canvas.loadPixels(); + for (int i = 0; i < canvas.pixels.length; i++) { + canvas.pixels[i] = BG_COLOR; + } + canvas.updatePixels(); + + // Draw the image on top + canvas.copy(image, 0, 0, image.width, image.height, 0, 0, image.width, image.height); + + return canvas; + } + + private int pixelmatch(PImage actual, PImage expected, PImage diff, int width, int height, double threshold) { + int diffCount = 0; + + actual.loadPixels(); + expected.loadPixels(); + diff.loadPixels(); + + for (int i = 0; i < actual.pixels.length; i++) { + int actualColor = actual.pixels[i]; + int expectedColor = expected.pixels[i]; + + double delta = colorDelta(actualColor, expectedColor); + + if (delta > threshold) { + // Mark as different (bright red pixel) + diff.pixels[i] = 0xFFFF0000; // Red + diffCount++; + } else { + // Mark as same (dimmed version of actual image) + int dimColor = dimColor(actualColor, ALPHA); + diff.pixels[i] = dimColor; + } + } + + diff.updatePixels(); + return diffCount; + } + + private double colorDelta(int color1, int color2) { + int r1 = (color1 >> 16) & 0xFF; + int g1 = (color1 >> 8) & 0xFF; + int b1 = color1 & 0xFF; + int a1 = (color1 >> 24) & 0xFF; + + int r2 = (color2 >> 16) & 0xFF; + int g2 = (color2 >> 8) & 0xFF; + int b2 = color2 & 0xFF; + int a2 = (color2 >> 24) & 0xFF; + + int dr = r1 - r2; + int dg = g1 - g2; + int db = b1 - b2; + int da = a1 - a2; + + return Math.sqrt(dr * dr + dg * dg + db * db + da * da) / 255.0; + } + + private int dimColor(int color, double alpha) { + int r = (int) (((color >> 16) & 0xFF) * alpha); + int g = (int) (((color >> 8) & 0xFF) * alpha); + int b = (int) ((color & 0xFF) * alpha); + int a = (int) (255 * alpha); + + r = Math.max(0, Math.min(255, r)); + g = Math.max(0, Math.min(255, g)); + b = Math.max(0, Math.min(255, b)); + a = Math.max(0, Math.min(255, a)); + + return (a << 24) | (r << 16) | (g << 8) | b; + } + + private boolean isDiffPixel(PImage image, int x, int y) { + if (x < 0 || x >= image.width || y < 0 || y >= image.height) return false; + + image.loadPixels(); + int color = image.pixels[y * image.width + x]; + + int r = (color >> 16) & 0xFF; + int g = (color >> 8) & 0xFF; + int b = color & 0xFF; + + return r == 255 && g == 0 && b == 0; + } + + private ClusterInfo findClusterSize(PImage diffImage, int startX, int startY, int width, int height, Set visited) { + List queue = new ArrayList<>(); + queue.add(new Point2D(startX, startY)); + + int size = 0; + List clusterPixels = new ArrayList<>(); + + while (!queue.isEmpty()) { + Point2D point = queue.remove(0); + int pos = point.y * width + point.x; + + // Skip if already visited + if (visited.contains(pos)) continue; + + // Skip if not a diff pixel + if (!isDiffPixel(diffImage, point.x, point.y)) continue; + + // Mark as visited + visited.add(pos); + size++; + clusterPixels.add(point); + + // Add neighbors to queue + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; + + int nx = point.x + dx; + int ny = point.y + dy; + + // Skip if out of bounds + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + + // Skip if already visited + int npos = ny * width + nx; + if (!visited.contains(npos)) { + queue.add(new Point2D(nx, ny)); + } + } + } + } + + // Determine if this is a line shift + boolean isLineShift = detectLineShift(clusterPixels, diffImage, width, height); + + return new ClusterInfo(size, clusterPixels, isLineShift); + } + + private boolean detectLineShift(List clusterPixels, PImage diffImage, int width, int height) { + if (clusterPixels.isEmpty()) return false; + + int linelikePixels = 0; + + for (Point2D pixel : clusterPixels) { + int neighbors = 0; + for (int dy = -1; dy <= 1; dy++) { + for (int dx = -1; dx <= 1; dx++) { + if (dx == 0 && dy == 0) continue; // Skip self + + int nx = pixel.x + dx; + int ny = pixel.y + dy; + + // Skip if out of bounds + if (nx < 0 || nx >= width || ny < 0 || ny >= height) continue; + + // Check if neighbor is a diff pixel + if (isDiffPixel(diffImage, nx, ny)) { + neighbors++; + } + } + } + + // Line-like pixels typically have 1-2 neighbors + if (neighbors <= 2) { + linelikePixels++; + } + } + + // If most pixels (>80%) in the cluster have ≤2 neighbors, it's likely a line shift + return (double) linelikePixels / clusterPixels.size() > 0.8; + } + + // Configuration methods + public ImageComparator setMaxSide(int maxSide) { + // For future configurability + return this; + } + + public ImageComparator setMinClusterSize(int minClusterSize) { + // For future configurability + return this; + } + + public ImageComparator setMaxTotalDiffPixels(int maxTotalDiffPixels) { + // For future configurability + return this; + } +} + +// Utility class for algorithm configuration +class ComparatorConfig { + public int maxSide = 400; + public int minClusterSize = 4; + public int maxTotalDiffPixels = 40; + public double threshold = 0.5; + public double alpha = 0.1; + public int backgroundColor = 0xFFFFFFFF; + + public ComparatorConfig() {} + + public ComparatorConfig(int maxSide, int minClusterSize, int maxTotalDiffPixels) { + this.maxSide = maxSide; + this.minClusterSize = minClusterSize; + this.maxTotalDiffPixels = maxTotalDiffPixels; + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/core/ProcessingSketch.java b/core/test/processing/visual/src/core/ProcessingSketch.java new file mode 100644 index 0000000000..e4750490b6 --- /dev/null +++ b/core/test/processing/visual/src/core/ProcessingSketch.java @@ -0,0 +1,9 @@ +package processing.visual.src.core; + +import processing.core.PApplet; + +// Interface for user sketches +public interface ProcessingSketch { + void setup(PApplet p); + void draw(PApplet p); +} diff --git a/core/test/processing/visual/src/core/TestConfig.java b/core/test/processing/visual/src/core/TestConfig.java new file mode 100644 index 0000000000..fd39bb91e7 --- /dev/null +++ b/core/test/processing/visual/src/core/TestConfig.java @@ -0,0 +1,33 @@ +package processing.visual.src.core; + +// Test configuration class +public class TestConfig { + public int width = 800; + public int height = 600; + public int[] backgroundColor = {255, 255, 255}; // RGB + public long renderWaitTime = 100; // milliseconds + public double threshold = 0.1; + + public TestConfig() {} + + public TestConfig(int width, int height) { + this.width = width; + this.height = height; + } + + public TestConfig(int width, int height, int[] backgroundColor) { + this.width = width; + this.height = height; + this.backgroundColor = backgroundColor; + } + + public TestConfig setThreshold(double threshold) { + this.threshold = threshold; + return this; + } + + public TestConfig setRenderWaitTime(long waitTime) { + this.renderWaitTime = waitTime; + return this; + } +} diff --git a/core/test/processing/visual/src/core/TestResult.java b/core/test/processing/visual/src/core/TestResult.java new file mode 100644 index 0000000000..6ff7c57ac7 --- /dev/null +++ b/core/test/processing/visual/src/core/TestResult.java @@ -0,0 +1,45 @@ +package processing.visual.src.core; + +// Enhanced test result with detailed information +public class TestResult { + public String testName; + public boolean passed; + public double mismatchRatio; + public String error; + public boolean isFirstRun; + public ComparisonDetails details; + + public TestResult(String testName, ComparisonResult comparison) { + this.testName = testName; + this.passed = comparison.passed; + this.mismatchRatio = comparison.mismatchRatio; + this.isFirstRun = comparison.isFirstRun; + this.details = comparison.details; + } + + public static TestResult createError(String testName, String error) { + TestResult result = new TestResult(); + result.testName = testName; + result.passed = false; + result.error = error; + return result; + } + + private TestResult() {} // For error constructor + + public void printResult() { + System.out.print(testName + ": "); + if (error != null) { + System.out.println("ERROR - " + error); + } else if (isFirstRun) { + System.out.println("BASELINE CREATED"); + } else if (passed) { + System.out.println("PASSED"); + } else { + System.out.println("FAILED (mismatch: " + String.format("%.4f", mismatchRatio * 100) + "%)"); + if (details != null) { + details.printDetails(); + } + } + } +} diff --git a/core/test/processing/visual/src/core/VisualTestRunner.java b/core/test/processing/visual/src/core/VisualTestRunner.java new file mode 100644 index 0000000000..7fd3a82a74 --- /dev/null +++ b/core/test/processing/visual/src/core/VisualTestRunner.java @@ -0,0 +1,263 @@ +package processing.visual.src.core; + +import processing.core.*; +import java.io.*; +import java.nio.file.*; +import java.util.*; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; + +// Core visual tester class +public class VisualTestRunner { + + private String screenshotDir; + private PixelMatchingAlgorithm pixelMatcher; + private String platform; + + public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher) { + this.pixelMatcher = pixelMatcher; + this.screenshotDir = "test/processing/visual/__screenshots__"; + this.platform = detectPlatform(); + createDirectoryIfNotExists(screenshotDir); + } + + public VisualTestRunner(PixelMatchingAlgorithm pixelMatcher, String screenshotDir) { + this.pixelMatcher = pixelMatcher; + this.screenshotDir = screenshotDir; + this.platform = detectPlatform(); + createDirectoryIfNotExists(screenshotDir); + } + + // Main test execution method + public TestResult runVisualTest(String testName, ProcessingSketch sketch) { + return runVisualTest(testName, sketch, new TestConfig()); + } + + public TestResult runVisualTest(String testName, ProcessingSketch sketch, TestConfig config) { + try { + System.out.println("Running visual test: " + testName); + + // Capture screenshot from sketch + PImage actualImage = captureSketch(sketch, config); + + // Compare with baseline + ComparisonResult comparison = compareWithBaseline(testName, actualImage, config); + + return new TestResult(testName, comparison); + + } catch (Exception e) { + return TestResult.createError(testName, e.getMessage()); + } + } + + // Capture PImage from Processing sketch + private PImage captureSketch(ProcessingSketch sketch, TestConfig config) { + SketchRunner runner = new SketchRunner(sketch, config); + runner.run(); + return runner.getImage(); + } + + // Compare actual image with baseline + private ComparisonResult compareWithBaseline(String testName, PImage actualImage, TestConfig config) { + String baselinePath = getBaselinePath(testName); + + PImage baselineImage = loadBaseline(baselinePath); + + if (baselineImage == null) { + // First run - save as baseline + saveBaseline(testName, actualImage); + return ComparisonResult.createFirstRun(); + } + + // Use your sophisticated pixel matching algorithm + ComparisonResult result = pixelMatcher.compare(baselineImage, actualImage, config.threshold); + + // Save diff images if test failed + if (!result.passed && result.diffImage != null) { + saveDiffImage(testName, result.diffImage); + } + + return result; + } + + // Save diff image for debugging + private void saveDiffImage(String testName, PImage diffImage) { + String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_]", "-"); + String diffPath; + if (sanitizedName.contains("/")) { + diffPath = "test/processing/visual/diff_" + sanitizedName.replace("/", "_") + "-" + platform + ".png"; + } else { + diffPath = "test/processing/visual/diff_" + sanitizedName + "-" + platform + ".png"; + } + + File diffFile = new File(diffPath); + diffFile.getParentFile().mkdirs(); + + diffImage.save(diffPath); + System.out.println("Diff image saved: " + diffPath); + } + + // Utility methods + private String detectPlatform() { + String os = System.getProperty("os.name").toLowerCase(); + if (os.contains("mac")) return "darwin"; + if (os.contains("win")) return "win32"; + return "linux"; + } + + private void createDirectoryIfNotExists(String dir) { + try { + Files.createDirectories(Paths.get(dir)); + } catch (IOException e) { + System.err.println("Failed to create directory: " + dir); + } + } + + private String getBaselinePath(String testName) { + String sanitizedName = testName.replaceAll("[^a-zA-Z0-9-_/]", "-"); + + return screenshotDir + "/" + sanitizedName + "-" + platform + ".png"; + } + + // Replace loadBaseline method: + private PImage loadBaseline(String path) { + File file = new File(path); + if (!file.exists()) { + System.out.println("loadBaseline: File doesn't exist: " + file.getAbsolutePath()); + return null; + } + + try { + System.out.println("loadBaseline: Loading from " + file.getAbsolutePath()); + + // Use Java ImageIO instead of PApplet + BufferedImage img = ImageIO.read(file); + + if (img == null) { + System.out.println("loadBaseline: ImageIO returned null"); + return null; + } + + // Convert BufferedImage to PImage + PImage pImg = new PImage(img.getWidth(), img.getHeight(), PImage.RGB); + img.getRGB(0, 0, pImg.width, pImg.height, pImg.pixels, 0, pImg.width); + pImg.updatePixels(); + + System.out.println("loadBaseline: ✓ Loaded " + pImg.width + "x" + pImg.height); + return pImg; + + } catch (Exception e) { + System.err.println("loadBaseline: Error loading image: " + e.getMessage()); + e.printStackTrace(); + return null; + } + } + + // Replace saveBaseline method: + private void saveBaseline(String testName, PImage image) { + String path = getBaselinePath(testName); + + if (image == null) { + System.out.println("saveBaseline: ✗ Image is null!"); + return; + } + + try { + // Convert PImage to BufferedImage + BufferedImage bImg = new BufferedImage(image.width, image.height, BufferedImage.TYPE_INT_RGB); + image.loadPixels(); + bImg.setRGB(0, 0, image.width, image.height, image.pixels, 0, image.width); + + // Create File object and ensure parent directories exist + File outputFile = new File(path); + outputFile.getParentFile().mkdirs(); // This creates nested directories + + // Use Java ImageIO to save + ImageIO.write(bImg, "PNG", outputFile); + + System.out.println("Baseline saved: " + path); + + } catch (Exception e) { + System.err.println("Failed to save baseline: " + path); + e.printStackTrace(); + } + } +} +class SketchRunner extends PApplet { + + private ProcessingSketch userSketch; + private TestConfig config; + private PImage capturedImage; + private volatile boolean rendered = false; + + public SketchRunner(ProcessingSketch userSketch, TestConfig config) { + this.userSketch = userSketch; + this.config = config; + } + + public void settings() { + size(config.width, config.height); + } + + public void setup() { + noLoop(); + + // Set background if specified + if (config.backgroundColor != null) { + background(config.backgroundColor[0], config.backgroundColor[1], config.backgroundColor[2]); + } + + // Call user setup + userSketch.setup(this); + } + + public void draw() { + if (!rendered) { + userSketch.draw(this); + capturedImage = get(); + rendered = true; + noLoop(); + } + } + + public void run() { + String[] args = {"SketchRunner"}; + PApplet.runSketch(args, this); + + // Simple polling with timeout + int maxWait = 100; // 10 seconds max + int waited = 0; + + while (!rendered && waited < maxWait) { + try { + Thread.sleep(100); + waited++; + + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + // Additional wait time + try { + Thread.sleep(config.renderWaitTime); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + if (surface != null) { + surface.setVisible(false); + } + try { + Thread.sleep(200); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + public PImage getImage() { + return capturedImage; + } +} From 5a0b5e7341f19a58984b7f962803362decd310f8 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 16 Oct 2025 16:56:13 +0530 Subject: [PATCH 21/29] added some tests ported by p5js --- .../visual/src/test/base/VisualTest.java | 61 +++ .../src/test/rendering/GradientTest.java | 34 ++ .../visual/src/test/shapes/Shape3DTest.java | 84 +++++ .../visual/src/test/shapes/ShapeTest.java | 356 ++++++++++++++++++ .../src/test/suites/RenderingSuite.java | 12 + .../visual/src/test/suites/ShapesSuite.java | 12 + 6 files changed, 559 insertions(+) create mode 100644 core/test/processing/visual/src/test/base/VisualTest.java create mode 100644 core/test/processing/visual/src/test/rendering/GradientTest.java create mode 100644 core/test/processing/visual/src/test/shapes/Shape3DTest.java create mode 100644 core/test/processing/visual/src/test/shapes/ShapeTest.java create mode 100644 core/test/processing/visual/src/test/suites/RenderingSuite.java create mode 100644 core/test/processing/visual/src/test/suites/ShapesSuite.java diff --git a/core/test/processing/visual/src/test/base/VisualTest.java b/core/test/processing/visual/src/test/base/VisualTest.java new file mode 100644 index 0000000000..55804b4acb --- /dev/null +++ b/core/test/processing/visual/src/test/base/VisualTest.java @@ -0,0 +1,61 @@ +package processing.visual.src.test.base; + +import org.junit.jupiter.api.*; +import processing.core.*; +import static org.junit.jupiter.api.Assertions.*; +import processing.visual.src.core.*; +import java.nio.file.*; +import java.io.File; + +/** + * Base class for Processing visual tests using JUnit 5 + */ +public abstract class VisualTest { + + protected static VisualTestRunner testRunner; + protected static ImageComparator comparator; + + @BeforeAll + public static void setupTestRunner() { + PApplet tempApplet = new PApplet(); + comparator = new ImageComparator(tempApplet); + testRunner = new VisualTestRunner(comparator); + + System.out.println("Visual test runner initialized"); + } + + /** + * Helper method to run a visual test + */ + protected void assertVisualMatch(String testName, ProcessingSketch sketch) { + assertVisualMatch(testName, sketch, new TestConfig()); + } + + protected void assertVisualMatch(String testName, ProcessingSketch sketch, TestConfig config) { + TestResult result = testRunner.runVisualTest(testName, sketch, config); + + // Print result for debugging + result.printResult(); + + // Handle different result types + if (result.isFirstRun) { + // First run - baseline created, mark as skipped + Assumptions.assumeTrue(false, "Baseline created for " + testName + ". Run tests again to verify."); + } else if (result.error != null) { + fail("Test error: " + result.error); + } else { + // Assert that the test passed + Assertions.assertTrue(result.passed, + String.format("Visual test '%s' failed with mismatch ratio: %.4f%%", + testName, result.mismatchRatio * 100)); + } + } + + /** + * Update baseline for a specific test (useful for maintenance) + */ + protected void updateBaseline(String testName, ProcessingSketch sketch, TestConfig config) { + BaselineManager manager = new BaselineManager(testRunner); + manager.updateBaseline(testName, sketch, config); + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/rendering/GradientTest.java b/core/test/processing/visual/src/test/rendering/GradientTest.java new file mode 100644 index 0000000000..b769109606 --- /dev/null +++ b/core/test/processing/visual/src/test/rendering/GradientTest.java @@ -0,0 +1,34 @@ +package processing.visual.src.test.rendering; + +import org.junit.jupiter.api.*; +import processing.core.*; +import processing.visual.src.test.base.VisualTest; +import processing.visual.src.core.ProcessingSketch; +import processing.visual.src.core.TestConfig; + +@Tag("rendering") +public class GradientTest extends VisualTest { + + @Test + @DisplayName("Linear gradient renders correctly") + public void testLinearGradient() { + TestConfig config = new TestConfig(600, 400); + + assertVisualMatch("rendering/linear-gradient", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.noStroke(); + } + + @Override + public void draw(PApplet p) { + for (int y = 0; y < p.height; y++) { + float inter = PApplet.map(y, 0, p.height, 0, 1); + int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); + p.stroke(c); + p.line(0, y, p.width, y); + } + } + }, config); + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/shapes/Shape3DTest.java b/core/test/processing/visual/src/test/shapes/Shape3DTest.java new file mode 100644 index 0000000000..7006cf329b --- /dev/null +++ b/core/test/processing/visual/src/test/shapes/Shape3DTest.java @@ -0,0 +1,84 @@ +package processing.visual.src.test.shapes; + +import org.junit.jupiter.api.*; +import processing.core.*; +import processing.visual.src.test.base.VisualTest; +import processing.visual.src.core.ProcessingSketch; +import processing.visual.src.core.TestConfig; + +@Tag("shapes") +@Tag("3d") +@Tag("p3d") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class Shape3DTest extends VisualTest { + + private ProcessingSketch create3DTest(Shape3DCallback callback) { + return new ProcessingSketch() { + @Override + public void setup(PApplet p) { + // P3D mode setup would go here if supported + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + callback.draw(p); + } + }; + } + + @FunctionalInterface + interface Shape3DCallback { + void draw(PApplet p); + } + + @Test + @DisplayName("3D vertex coordinates") + public void test3DVertexCoordinates() { + assertVisualMatch("shapes-3d/vertex-coordinates", create3DTest(p -> { + p.beginShape(PApplet.QUAD_STRIP); + p.vertex(10, 10, 0); + p.vertex(10, 40, -150); + p.vertex(40, 10, 150); + p.vertex(40, 40, 200); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @DisplayName("Per-vertex fills") + public void testPerVertexFills() { + assertVisualMatch("shapes-3d/per-vertex-fills", create3DTest(p -> { + p.beginShape(PApplet.QUAD_STRIP); + p.fill(0); + p.vertex(10, 10); + p.fill(255, 0, 0); + p.vertex(45, 5); + p.fill(0, 255, 0); + p.vertex(15, 35); + p.fill(255, 255, 0); + p.vertex(40, 45); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @DisplayName("Per-vertex strokes") + public void testPerVertexStrokes() { + assertVisualMatch("shapes-3d/per-vertex-strokes", create3DTest(p -> { + p.strokeWeight(5); + p.beginShape(PApplet.QUAD_STRIP); + p.stroke(0); + p.vertex(10, 10); + p.stroke(255, 0, 0); + p.vertex(45, 5); + p.stroke(0, 255, 0); + p.vertex(15, 35); + p.stroke(255, 255, 0); + p.vertex(40, 45); + p.endShape(); + }), new TestConfig(50, 50)); + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/shapes/ShapeTest.java b/core/test/processing/visual/src/test/shapes/ShapeTest.java new file mode 100644 index 0000000000..47ae08b5f3 --- /dev/null +++ b/core/test/processing/visual/src/test/shapes/ShapeTest.java @@ -0,0 +1,356 @@ +package processing.visual.src.test.shapes; + +import org.junit.jupiter.api.*; +import processing.core.*; +import processing.visual.src.test.base.VisualTest; +import processing.visual.src.core.ProcessingSketch; +import processing.visual.src.core.TestConfig; + +@Tag("shapes") +@Tag("rendering") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ShapeTest extends VisualTest { + + // Helper method for common setup + private ProcessingSketch createShapeTest(ShapeDrawingCallback callback) { + return new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + callback.draw(p); + } + }; + } + + @FunctionalInterface + interface ShapeDrawingCallback { + void draw(PApplet p); + } + + // ========== Polylines ========== + + @Test + @Order(1) + @Tag("polylines") + @DisplayName("Drawing polylines") + public void testPolylines() { + assertVisualMatch("shapes/polylines", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(2) + @Tag("polylines") + @DisplayName("Drawing closed polylines") + public void testClosedPolylines() { + assertVisualMatch("shapes/closed-polylines", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(15, 25); + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + // ========== Contours ========== + + @Test + @Order(3) + @Tag("contours") + @DisplayName("Drawing with contours") + public void testContours() { + assertVisualMatch("shapes/contours", createShapeTest(p -> { + p.beginShape(); + // Outer circle + vertexCircle(p, 15, 15, 10, 1); + + // Inner cutout + p.beginContour(); + vertexCircle(p, 15, 15, 5, -1); + p.endContour(); + + // Second outer shape + p.beginContour(); + vertexCircle(p, 30, 30, 8, -1); + p.endContour(); + + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(4) + @Tag("contours") + @DisplayName("Drawing with a single closed contour") + public void testSingleClosedContour() { + assertVisualMatch("shapes/single-closed-contour", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(40, 10); + p.vertex(40, 40); + p.vertex(10, 40); + + p.beginContour(); + p.vertex(20, 20); + p.vertex(20, 30); + p.vertex(30, 30); + p.vertex(30, 20); + p.endContour(); + + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + @Test + @Order(5) + @Tag("contours") + @DisplayName("Drawing with a single unclosed contour") + public void testSingleUnclosedContour() { + assertVisualMatch("shapes/single-unclosed-contour", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.vertex(40, 10); + p.vertex(40, 40); + p.vertex(10, 40); + + p.beginContour(); + p.vertex(20, 20); + p.vertex(20, 30); + p.vertex(30, 30); + p.vertex(30, 20); + p.endContour(); + + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + // ========== Triangle Shapes ========== + + @Test + @Order(6) + @Tag("triangles") + @DisplayName("Drawing triangle fans") + public void testTriangleFans() { + assertVisualMatch("shapes/triangle-fans", createShapeTest(p -> { + p.beginShape(PApplet.TRIANGLE_FAN); + p.vertex(25, 25); + for (int i = 0; i <= 12; i++) { + float angle = PApplet.map(i, 0, 12, 0, PApplet.TWO_PI); + p.vertex(25 + 10 * PApplet.cos(angle), 25 + 10 * PApplet.sin(angle)); + } + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(7) + @Tag("triangles") + @DisplayName("Drawing triangle strips") + public void testTriangleStrips() { + assertVisualMatch("shapes/triangle-strips", createShapeTest(p -> { + p.beginShape(PApplet.TRIANGLE_STRIP); + p.vertex(10, 10); + p.vertex(30, 10); + p.vertex(15, 20); + p.vertex(35, 20); + p.vertex(10, 40); + p.vertex(30, 40); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(8) + @Tag("triangles") + @DisplayName("Drawing with triangles") + public void testTriangles() { + assertVisualMatch("shapes/triangles", createShapeTest(p -> { + p.beginShape(PApplet.TRIANGLES); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(10, 10); + p.vertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Quad Shapes ========== + + @Test + @Order(9) + @Tag("quads") + @DisplayName("Drawing quad strips") + public void testQuadStrips() { + assertVisualMatch("shapes/quad-strips", createShapeTest(p -> { + p.beginShape(PApplet.QUAD_STRIP); + p.vertex(10, 10); + p.vertex(30, 10); + p.vertex(15, 20); + p.vertex(35, 20); + p.vertex(10, 40); + p.vertex(30, 40); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(10) + @Tag("quads") + @DisplayName("Drawing with quads") + public void testQuads() { + assertVisualMatch("shapes/quads", createShapeTest(p -> { + p.beginShape(PApplet.QUADS); + p.vertex(10, 10); + p.vertex(15, 10); + p.vertex(15, 15); + p.vertex(10, 15); + p.vertex(25, 25); + p.vertex(30, 25); + p.vertex(30, 30); + p.vertex(25, 30); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Curves ========== + + @Test + @Order(11) + @Tag("curves") + @DisplayName("Drawing with curves") + public void testCurves() { + assertVisualMatch("shapes/curves", createShapeTest(p -> { + p.beginShape(); + p.curveVertex(10, 10); + p.curveVertex(15, 40); + p.curveVertex(40, 35); + p.curveVertex(25, 15); + p.curveVertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(12) + @Tag("curves") + @DisplayName("Drawing closed curves") + public void testClosedCurves() { + assertVisualMatch("shapes/closed-curves", createShapeTest(p -> { + p.beginShape(); + p.curveVertex(10, 10); + p.curveVertex(15, 40); + p.curveVertex(40, 35); + p.curveVertex(25, 15); + p.curveVertex(15, 25); + p.endShape(PApplet.CLOSE); + }), new TestConfig(50, 50)); + } + + @Test + @Order(13) + @Tag("curves") + @DisplayName("Drawing with curves with tightness") + public void testCurvesWithTightness() { + assertVisualMatch("shapes/curves-tightness", createShapeTest(p -> { + p.curveTightness(-1); + p.beginShape(); + p.curveVertex(10, 10); + p.curveVertex(15, 40); + p.curveVertex(40, 35); + p.curveVertex(25, 15); + p.curveVertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Bezier Curves ========== + + @Test + @Order(14) + @Tag("bezier") + @DisplayName("Drawing with bezier curves") + public void testBezierCurves() { + assertVisualMatch("shapes/bezier-curves", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.bezierVertex(10, 40, 40, 40, 40, 10); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(15) + @Tag("bezier") + @DisplayName("Drawing with quadratic beziers") + public void testQuadraticBeziers() { + assertVisualMatch("shapes/quadratic-beziers", createShapeTest(p -> { + p.beginShape(); + p.vertex(10, 10); + p.quadraticVertex(25, 40, 40, 10); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Points and Lines ========== + + @Test + @Order(16) + @Tag("primitives") + @DisplayName("Drawing with points") + public void testPoints() { + assertVisualMatch("shapes/points", createShapeTest(p -> { + p.strokeWeight(5); + p.beginShape(PApplet.POINTS); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.vertex(15, 25); + p.endShape(); + }), new TestConfig(50, 50)); + } + + @Test + @Order(17) + @Tag("primitives") + @DisplayName("Drawing with lines") + public void testLines() { + assertVisualMatch("shapes/lines", createShapeTest(p -> { + p.beginShape(PApplet.LINES); + p.vertex(10, 10); + p.vertex(15, 40); + p.vertex(40, 35); + p.vertex(25, 15); + p.endShape(); + }), new TestConfig(50, 50)); + } + + // ========== Helper Methods ========== + + /** + * Helper method to create a circle using vertices + */ + private void vertexCircle(PApplet p, float x, float y, float r, int direction) { + for (int i = 0; i <= 12; i++) { + float angle = PApplet.map(i, 0, 12, 0, PApplet.TWO_PI) * direction; + p.vertex(x + r * PApplet.cos(angle), y + r * PApplet.sin(angle)); + } + } +} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/suites/RenderingSuite.java b/core/test/processing/visual/src/test/suites/RenderingSuite.java new file mode 100644 index 0000000000..24187a72a3 --- /dev/null +++ b/core/test/processing/visual/src/test/suites/RenderingSuite.java @@ -0,0 +1,12 @@ +package processing.visual.src.test.suites; + +import org.junit.platform.suite.api.*; + +@Suite +@SuiteDisplayName("Rendering Tests") +@SelectPackages("processing.visual.src.test.rendering") +@ExcludePackages("processing.visual.src.test.suites") +@IncludeTags("rendering") +public class RenderingSuite { + // Empty class - just holds annotations +} diff --git a/core/test/processing/visual/src/test/suites/ShapesSuite.java b/core/test/processing/visual/src/test/suites/ShapesSuite.java new file mode 100644 index 0000000000..f45e472826 --- /dev/null +++ b/core/test/processing/visual/src/test/suites/ShapesSuite.java @@ -0,0 +1,12 @@ +package processing.visual.src.test.suites; + +import org.junit.platform.suite.api.*; + +@Suite +@SuiteDisplayName("Basic Shapes Visual Tests") +@SelectPackages("processing.visual.src.test.shapes") +@ExcludePackages("processing.visual.src.test.suites") +@IncludeTags("shapes") +public class ShapesSuite { + // Empty class - just holds annotations +} \ No newline at end of file From fa72d2d4c3a77d7b3d41dd3b14a74fc5e2866a4b Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 16 Oct 2025 16:57:28 +0530 Subject: [PATCH 22/29] removing test rendering suite and its test file --- .../src/test/rendering/GradientTest.java | 34 ------------------- .../src/test/suites/RenderingSuite.java | 12 ------- 2 files changed, 46 deletions(-) delete mode 100644 core/test/processing/visual/src/test/rendering/GradientTest.java delete mode 100644 core/test/processing/visual/src/test/suites/RenderingSuite.java diff --git a/core/test/processing/visual/src/test/rendering/GradientTest.java b/core/test/processing/visual/src/test/rendering/GradientTest.java deleted file mode 100644 index b769109606..0000000000 --- a/core/test/processing/visual/src/test/rendering/GradientTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package processing.visual.src.test.rendering; - -import org.junit.jupiter.api.*; -import processing.core.*; -import processing.visual.src.test.base.VisualTest; -import processing.visual.src.core.ProcessingSketch; -import processing.visual.src.core.TestConfig; - -@Tag("rendering") -public class GradientTest extends VisualTest { - - @Test - @DisplayName("Linear gradient renders correctly") - public void testLinearGradient() { - TestConfig config = new TestConfig(600, 400); - - assertVisualMatch("rendering/linear-gradient", new ProcessingSketch() { - @Override - public void setup(PApplet p) { - p.noStroke(); - } - - @Override - public void draw(PApplet p) { - for (int y = 0; y < p.height; y++) { - float inter = PApplet.map(y, 0, p.height, 0, 1); - int c = p.lerpColor(p.color(255, 0, 0), p.color(0, 0, 255), inter); - p.stroke(c); - p.line(0, y, p.width, y); - } - } - }, config); - } -} \ No newline at end of file diff --git a/core/test/processing/visual/src/test/suites/RenderingSuite.java b/core/test/processing/visual/src/test/suites/RenderingSuite.java deleted file mode 100644 index 24187a72a3..0000000000 --- a/core/test/processing/visual/src/test/suites/RenderingSuite.java +++ /dev/null @@ -1,12 +0,0 @@ -package processing.visual.src.test.suites; - -import org.junit.platform.suite.api.*; - -@Suite -@SuiteDisplayName("Rendering Tests") -@SelectPackages("processing.visual.src.test.rendering") -@ExcludePackages("processing.visual.src.test.suites") -@IncludeTags("rendering") -public class RenderingSuite { - // Empty class - just holds annotations -} From d435e62a33adfc595a57b5a17793f19d0ccad678 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 16 Oct 2025 16:57:54 +0530 Subject: [PATCH 23/29] added screenshots --- .../rendering/linear-gradient-linux.png | Bin 0 -> 2943 bytes .../shapes-3d/per-vertex-fills-linux.png | Bin 0 -> 661 bytes .../shapes-3d/per-vertex-strokes-linux.png | Bin 0 -> 696 bytes .../shapes-3d/vertex-coordinates-linux.png | Bin 0 -> 134 bytes .../shapes/bezier-curves-linux.png | Bin 0 -> 722 bytes .../shapes/closed-curves-linux.png | Bin 0 -> 723 bytes .../shapes/closed-polylines-linux.png | Bin 0 -> 602 bytes .../__screenshots__/shapes/contours-linux.png | Bin 0 -> 991 bytes .../__screenshots__/shapes/curves-linux.png | Bin 0 -> 702 bytes .../shapes/curves-tightness-linux.png | Bin 0 -> 761 bytes .../__screenshots__/shapes/lines-linux.png | Bin 0 -> 387 bytes .../__screenshots__/shapes/points-linux.png | Bin 0 -> 254 bytes .../__screenshots__/shapes/polylines-linux.png | Bin 0 -> 583 bytes .../__screenshots__/shapes/quad-strips-linux.png | Bin 0 -> 405 bytes .../shapes/quadratic-beziers-linux.png | Bin 0 -> 579 bytes .../__screenshots__/shapes/quads-linux.png | Bin 0 -> 188 bytes .../shapes/single-closed-contour-linux.png | Bin 0 -> 222 bytes .../shapes/single-unclosed-contour-linux.png | Bin 0 -> 222 bytes .../shapes/triangle-fans-linux.png | Bin 0 -> 968 bytes .../shapes/triangle-strips-linux.png | Bin 0 -> 692 bytes .../__screenshots__/shapes/triangles-linux.png | Bin 0 -> 686 bytes 21 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-fills-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-strokes-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/closed-curves-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/closed-polylines-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/contours-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/curves-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/lines-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/points-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/polylines-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/quadratic-beziers-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/quads-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/single-closed-contour-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/triangle-strips-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/triangles-linux.png diff --git a/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png b/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..87bb74dfc26bbc8a7eb9abf7333500b87bec0a24 GIT binary patch literal 2943 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png b/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e07fe529c8ee84295a8d219564ef7954d4f5ca85 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AzMd|QAr*0N&n;wRFyLv}@UQ;n zxmoMGS1Sk|-7`6@UHX<#Zs#_a=ta60r)79=QN_=-JNe?t>YUel7l8&dc)I$ztaD0e F0suSKG4B8X literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png b/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e628f405416f0c1608764e2352c817234fcbda87 GIT binary patch literal 722 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0FXZV4CUa;uum9_ja1CM^T`}@!Kvg zAC*NG^3QQmWD>o2^q{7JifdGei%67|NDRx-g^I<21qFIXjyeiVj4CO(P^R7~;J876 zM`+fn?+eA_*7aZAdHgXq-*@xQIluq^e^4<0cg|O{NxFZIrzvDygd>LwmtKFBmzSUa z`q|&Vwe|J#(+d?cI*&fuzTJGzsWUZp;+-x#dYB!(7Y3Y3GX{!${=7Lv>u9PBN0ha- z_2P>h%Z-dBcub^t^&UH1Fq(b#`}gk_Q)h;)PBoD#lw@zZcJX51+OWeb&&0&XTg&jp z9BW)qT2@w8TG|>s(^GA7qXPrTy4bilwr0mUr+RL`^;>=!WZmkZl@dH`b561GIL-A7 z*P9M9D{TAi(3K&RPqL&NfB5l3WBO@DNswknz3H!?J!4BZ4$u&p99wC{@i)~`C*f4 zMP}&g(5+Et{}sygANN`+bgAH=(4H*~moHy-QEIH#;A}rE*U#==&mO<__U-5&J2utG z&p!L?+c&c(4_>@~u=?unD%C_DzvaO(Cz>A?aGd5)d2m5~!3CxVVsFZRzkaQ~?(fv) z%a#w&)t^M=db^m>P3kwTN%b&GtlwWMAicg#9;j;Sbwb!L< zHgM>?`B@{kOk~dTqE~Ade4U=OamV)U?BZPamp)DX|MV#<-wdB+ckjmjXq10;uq(=8 z`Q^r~$I~_+4CA}b2$i*)F$H}hIMP!eeK`9dw0-Emfyi9QoY9? v?~GY@G2?;rlr@g8>*1*anXzGER6WDBfW`0DPwz1Vrd0+{S3j3^P6(U7{_NTQ6oai*oln=O{SDA$ZGb;ia!APv6{)kb_UU;M6xr}(5B0n^wQyzr4H*27|oF@urnZrEYI;eLkOFudmnZ_%KlxAElDaMfa${V0hnD3Yivl*%ymNGMUt9G&Y;<`S}^=vLMqU zE&F=CE|<$LmrE*@!Z!#06yd}Z5NN@EhO)!S>Fw?ZS_o!>9QvcD1wAAAQ!OM=moV6-F{EeV8HqtRHcR`}F^2(3gS(d+f_ zss9jK#bPm^&*M}7A+(y!W-u81{2mFd!C+un7ITlw*KJt`-#aU7>!ub0bZ{DvG_ z3&xa6rN8#*b^@-~D_o<~>EQQ-&|0vXR;v{bhoAR|oD_${K|f@)7L>7CtriLew8xiD ziqUAK88TW6%9zXLhQpzIk8hk5njxdLpp2nV=yWR$XHrX#%{NJKA++Cd?$sskg>E>8DYlpc*J8VyoHRVrOJp0 zvhffymX<2x?RLWh*+@gi(z5Sm#M2~*LdMlnWyGT-h(gBIQf0)Wgz!Vg)q0ds6K2S` zTAF2a!wDHzOTUZ{I3eR|+1h2iTrTVNy5H~DYPDD_20#3Z#RAW`BhZqT(d_|Ofgtm+>Dx5CSbIV=9&M$pazKf~o#_AOu>#Znwin o{vgx(7)DEi(UM@aBz&pX4@Y3iAecJL&;S4c07*qoM6N<$g28DYmjD0& literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/contours-linux.png b/core/test/processing/visual/__screenshots__/shapes/contours-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..032c60a7538965af172c780806541a149a2616ee GIT binary patch literal 991 zcmV<510ei~P)FV-VrRjE?m|K&N=lNCvJwkq!NS7l=H??wu~8IBzLI1oHtZ=2 zA7!cc{bg!8J$LT8Gk4~`dF%cb)7&#>&iQrDxzF=V!u78tK{osj;#yAlKea9{E;cqc z#>dCo+S*D=N)i(j{eFK_Q`5-E$m;4UPvr|9q0?gGw6wI+($enk?)mxo+uPgs_jj(` z(^6bqoRX5VwY4Q*^MbHiIOXKzoSvTk2~RK>%*)H8k9^rR)>?L)3_3bG0)c>h-!&Fm z_MPDC>#L}!XliOoj^Ylfg-u3AhU&z9Qc_Za0zcIb_xJainVCmNM`9>9NUg=i#j2_* z)hRnWdueG&?ZZzRdGzt|VRUP6Zyz2WR{OX_YW4Q^j*gC+S}crkGN94t^DQha7~Rg# z&pG?DlMAF)d3pKq@v*7JmWd7~H8nNq>FH+A{QUgogq}vTSo*fla-Z~+S=OG)KsqAi>G=g z`2PN`uC68`iWkFSS4#uuiM`V}89Y8d673ET59Rw-c%a1@S4%Vv{1rMoJMAO`544y} zEzvYQKR>gs6%`e7h!8x`A{MA6nx6jteogu}iQFS1#3y2boIecwo&S-RC}{N%X!stM zLxkXgmMH%<7$yJy{=OU{1P`>t$C3v7A4nc(5wXZE#BpM#6j3k=B%6hX2U@VZyGw2% zjz2v;Nu3{X%F4>JUubxv1>`$&3v-KtjC)#GC;Lv<9%>n|N5pX|3*ytw%}q~F51%2e zt*r+K2ePXckF^Ytpo}K3b9E|Y5ECu;#I>9d*K$H!%L#EUC&aa!@E6i*VdEdxhmimP N002ovPDHLkV1ic0*?ufv)TB3KD}PwXf*I`Lio{|&1SpZ?)`(u$48}7 zSt^zAAQJe|TCdj!rvsD8+3613xKMDStgUgLob5|Eigb%2bRkv zm}RqBJmCU((1NmKqa*YAT%k~u%Vj+0C%9@c%YKr6zYk`lYm=)MD*H()m5NrY?e%(i z@_D#wvCDpvcsy>gSYUIN}VPw7w}j8yKuosS1SxPB{g= z){kXp1NDa&K)2h)SuE(aE-5>kP$=Z_cyJaAdaVn~j@WLu8jYq{EaJGYP-nxh%-ENX!2G{9LV8Z*Om~=dG zo+3xW;jq{1bvPVyxm+X?nM@|X-w*pZsm&*3S_j~K^6>BgODV`87K`Qc`BtliM^izg k^$!rVND#D05VT163!sr))f)NWK>z>%07*qoM6N<$f@;K21ONa4 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png b/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..aecfee50eac36f1b58ad3c38476e9db5d17ffb4c GIT binary patch literal 761 zcmVsCmYl~g(zLZCDB106h#EVABzwKiqzCngMpym*~vj@iBbenQjwss zDFRUu!^VmxrSFI2;{2T6*XN!aFsZ>OiH+uPfAyPZ-_9IeO4$H`>! z{lmw{$K~ZE$VDQN`}=!5bT!1$>UO&`r(Z^+Q6Lb|YPI=%9*h*eYA`9VKL}f?xdcDof z&1SQSLm7l?k(M2?ySsaJb%jG21ZoK=I|5G0a5#)}83bwxD?6J^CgXHEaV~=pEt<0b zlFep$E?`2mXv+S}$;nAD7{t*GLbNE$K4X7>ANC>~%|NV0Uv}W-(^-&d(U<-G{oQCZrcx;!Jr9`{W!b;JzHBxd+(YAB z4rE&JL36nrUTrntK@EhgR%@%(!nqvCw2H+d++ITZa5%)v@&YRG%ncLU?KTcw1eq29 zXEi+0E0xN0I{o_kir41=b&SX33WehA>OOQ5b`#tpJr4 r8@LYrVW9k9@IG&Kr2Et}jXCAmtp)3CXp=W5rH2&O)st6aw-ntFh|<1-xH+On2f zC!IO5=gIN-cy5lh6+IR$*K`e=1ft51b~t3*l~~NVP*u_{@b!GK_b+Ubm?d^tJY;_S V@wSBadBDJB@O1TaS?83{1OQeUr{Dkp literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/points-linux.png b/core/test/processing/visual/__screenshots__/shapes/points-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d0aecf8d308801172759a216d5c9a5373da55412 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AM?GB}Ln`9lPBr8^s=(tM`Oxmp z?Bp*IXFvUKVA-S^Z1aRyG;7H#)6z?yB7&Yt%PkRe&N5sgb~q@z=VJBxt;bBy75_Bw zUb{il{*lde{>@ji*8bfJVHv%hmbp#i$@>KtC)F4BPOGsqiCT8o=3jff}k2tuN|JJ}OAcZW4I z_w3my&R5R4$$T=uUm@gtBq2Qb46K#}t0lo|Nw8WH2(4r?x!rE@)I5Y%DwUc{CU|Nd zLaS6N_4|E1H4mZHYPFipCZ3vy&>9Q|<#HKMe_^#+;ZaZst@(VO&*$;<6^q3p7K=@% zQ~VGJLJMjbiA47MJ%0EJIK|^}xOh69-fTAbSq`BE78tDVnhG=J%& z7!HS;A)~dRjD!632plq&mMSBxDHe--Cq=N3v9wef@j@QKLdMckWyA~F1PB>R%PS*Z$VM76 zmX`A8smRCj{_#xwJ>6FnAGh|#X%`*Dogp8}DUq%m3$hcaL zb{Q|1%X+=;^?LPsJ&{O6qtWGZiFZ5@Xi3ZH_kf=i4u`YZY_(c#x7(x9=>4B{ffiXt z9S?*+>$!}7=z$PuK^Zfdj3Ez%Knu2-^FRo+fZc9~M`n;|{To(Gg4L2>wIqC~))x|j V=vi=ANS6Qr002ovPDHLkV1fs&1S0?d literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png b/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a0836a3a6d019352d0e4cdb251912f5ea60cc2f7 GIT binary patch literal 405 zcmV;G0c!q&4g4&>0f#w$6U_Cg3LEz|{tiz%IAKeK|HKJ$5JV`R$kD@~6 zK|jfZKlly2mISXQ!D~tIS`x0brfG`f_;Hsgip&H)6yr+E@Or(PrunyoXnj7PvMhNH7{f5+dCn)EBhYHww(B~+5W{@Lx~}=ea|BvNQ7p^C z7h*i0&%W>Z#B)%sZQGJ0IgW!b1laf8+(g!OyV+M8e@Pp87PO&ld~^@v;jXS)EQtDwWP=Gd%bS)9F+w6t-F|yzBx;7Dy(O9*+ml ze$@GVb~qf-XcVvci92gB7$_8qUayBYJfvE!%H{HYzmM1a#GM67rIJ>w-S78!3DI`D zg}JbllS{^)_csuDdqp-n{#3&i9=uN|~k% zl8TrUY95u5RXR;f>dMswr{$jE&75=9VtLg{W|f%ExR7}Xhv2Sxc9)%kt2cMROeXot_v8w%{@Myh7VkmbX{603FQW>FVdQ&MBb@0K0oz0RR91 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png b/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..401b9974d104fb6c160742813712fd19b64d37b2 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AOFdm2Ln`9lp4-UFY{=sr_}2br z?o@`ADq?0G{EFcprkZGMo)@mgdrxt*%*>}JbllS{^)_csuDdqp-n{#3&i9=uN|~k% zl8TrUY95u5RXR;f>dMswr{$jE&75=9VtLg{W|f%ExR7}Xhv2Sxc9)%kt2cMROeXot_v8w%{@Myh7VkmbX{603FQW>FVdQ&MBb@0K0oz0RR91 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png b/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c2b87e6474ded14d75d91e6e02c3c0fbaed4e9 GIT binary patch literal 968 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 UYk7IszyJUM07*qoM6N<$g7s=T0RR91 literal 0 HcmV?d00001 From 0d910010c2d9cdac1f1d7e9ad910c4f562482e31 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 16 Oct 2025 16:58:11 +0530 Subject: [PATCH 24/29] config files --- gradle/libs.versions.toml | 4 ++++ settings.gradle.kts | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfacae1ead..ccd1f5c6d4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,6 +3,8 @@ kotlin = "2.0.20" compose-plugin = "1.7.1" jogl = "2.5.0" jupiter = "5.12.0" +junitPlatform = "1.12.0" +assertj = "3.24.2" [libraries] jogl = { module = "org.jogamp.jogl:jogl-all-main", version.ref = "jogl" } @@ -29,6 +31,8 @@ markdown = { module = "com.mikepenz:multiplatform-markdown-renderer-m2", version markdownJVM = { module = "com.mikepenz:multiplatform-markdown-renderer-jvm", version = "0.31.0" } clikt = { module = "com.github.ajalt.clikt:clikt", version = "5.0.2" } kotlinxSerializationJson = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version = "1.6.3" } +junitPlatformSuite = { module = "org.junit.platform:junit-platform-suite", version.ref = "junitPlatform" } +assertjCore = { module = "org.assertj:assertj-core", version.ref = "assertj" } [plugins] jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "compose-plugin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index b0be4a3763..4d24671266 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -10,8 +10,6 @@ include( "java:libraries:net", "java:libraries:pdf", "java:libraries:serial", - "java:libraries:svg", - ":visual-tests" + "java:libraries:svg" ) -include("app:utils") -include(":visual-tests") \ No newline at end of file +include("app:utils") \ No newline at end of file From 66071ac191d24a4a25d8d8257f58cbeb957c6f35 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Wed, 22 Oct 2025 00:39:31 +0530 Subject: [PATCH 25/29] fixed the pixeldensity to 1 --- .../rendering/linear-gradient-linux.png | Bin 2943 -> 0 bytes .../shapes-3d/per-vertex-fills-linux.png | Bin 661 -> 0 bytes .../shapes-3d/per-vertex-strokes-linux.png | Bin 696 -> 0 bytes .../shapes-3d/vertex-coordinates-linux.png | Bin 134 -> 0 bytes .../shapes/bezier-curves-linux.png | Bin 722 -> 0 bytes .../shapes/closed-curves-linux.png | Bin 723 -> 0 bytes .../shapes/closed-polylines-linux.png | Bin 602 -> 0 bytes .../__screenshots__/shapes/contours-linux.png | Bin 991 -> 0 bytes .../__screenshots__/shapes/curves-linux.png | Bin 702 -> 0 bytes .../shapes/curves-tightness-linux.png | Bin 761 -> 0 bytes .../__screenshots__/shapes/lines-linux.png | Bin 387 -> 0 bytes .../__screenshots__/shapes/points-linux.png | Bin 254 -> 0 bytes .../__screenshots__/shapes/polylines-linux.png | Bin 583 -> 0 bytes .../__screenshots__/shapes/quad-strips-linux.png | Bin 405 -> 0 bytes .../shapes/quadratic-beziers-linux.png | Bin 579 -> 0 bytes .../__screenshots__/shapes/quads-linux.png | Bin 188 -> 0 bytes .../shapes/single-closed-contour-linux.png | Bin 222 -> 0 bytes .../shapes/single-unclosed-contour-linux.png | Bin 222 -> 0 bytes .../shapes/triangle-fans-linux.png | Bin 968 -> 0 bytes .../shapes/triangle-strips-linux.png | Bin 692 -> 0 bytes .../__screenshots__/shapes/triangles-linux.png | Bin 686 -> 0 bytes .../visual/src/core/VisualTestRunner.java | 1 + 22 files changed, 1 insertion(+) delete mode 100644 core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-fills-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-strokes-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/closed-curves-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/closed-polylines-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/contours-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/curves-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/lines-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/points-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/polylines-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/quadratic-beziers-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/quads-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/single-closed-contour-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/triangle-strips-linux.png delete mode 100644 core/test/processing/visual/__screenshots__/shapes/triangles-linux.png diff --git a/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png b/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png deleted file mode 100644 index 87bb74dfc26bbc8a7eb9abf7333500b87bec0a24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2943 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 diff --git a/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png b/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png deleted file mode 100644 index e07fe529c8ee84295a8d219564ef7954d4f5ca85..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AzMd|QAr*0N&n;wRFyLv}@UQ;n zxmoMGS1Sk|-7`6@UHX<#Zs#_a=ta60r)79=QN_=-JNe?t>YUel7l8&dc)I$ztaD0e F0suSKG4B8X diff --git a/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png b/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png deleted file mode 100644 index e628f405416f0c1608764e2352c817234fcbda87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 722 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0FXZV4CUa;uum9_ja1CM^T`}@!Kvg zAC*NG^3QQmWD>o2^q{7JifdGei%67|NDRx-g^I<21qFIXjyeiVj4CO(P^R7~;J876 zM`+fn?+eA_*7aZAdHgXq-*@xQIluq^e^4<0cg|O{NxFZIrzvDygd>LwmtKFBmzSUa z`q|&Vwe|J#(+d?cI*&fuzTJGzsWUZp;+-x#dYB!(7Y3Y3GX{!${=7Lv>u9PBN0ha- z_2P>h%Z-dBcub^t^&UH1Fq(b#`}gk_Q)h;)PBoD#lw@zZcJX51+OWeb&&0&XTg&jp z9BW)qT2@w8TG|>s(^GA7qXPrTy4bilwr0mUr+RL`^;>=!WZmkZl@dH`b561GIL-A7 z*P9M9D{TAi(3K&RPqL&NfB5l3WBO@DNswknz3H!?J!4BZ4$u&p99wC{@i)~`C*f4 zMP}&g(5+Et{}sygANN`+bgAH=(4H*~moHy-QEIH#;A}rE*U#==&mO<__U-5&J2utG z&p!L?+c&c(4_>@~u=?unD%C_DzvaO(Cz>A?aGd5)d2m5~!3CxVVsFZRzkaQ~?(fv) z%a#w&)t^M=db^m>P3kwTN%b&GtlwWMAicg#9;j;Sbwb!L< zHgM>?`B@{kOk~dTqE~Ade4U=OamV)U?BZPamp)DX|MV#<-wdB+ckjmjXq10;uq(=8 z`Q^r~$I~_+4CA}b2$i*)F$H}hIMP!eeK`9dw0-Emfyi9QoY9? v?~GY@G2?;rlr@g8>*1*anXzGER6WDBfW`0DPwz1Vrd0+{S3j3^P6(U7{_NTQ6oai*oln=O{SDA$ZGb;ia!APv6{)kb_UU;M6xr}(5B0n^wQyzr4H*27|oF@urnZrEYI;eLkOFudmnZ_%KlxAElDaMfa${V0hnD3Yivl*%ymNGMUt9G&Y;<`S}^=vLMqU zE&F=CE|<$LmrE*@!Z!#06yd}Z5NN@EhO)!S>Fw?ZS_o!>9QvcD1wAAAQ!OM=moV6-F{EeV8HqtRHcR`}F^2(3gS(d+f_ zss9jK#bPm^&*M}7A+(y!W-u81{2mFd!C+un7ITlw*KJt`-#aU7>!ub0bZ{DvG_ z3&xa6rN8#*b^@-~D_o<~>EQQ-&|0vXR;v{bhoAR|oD_${K|f@)7L>7CtriLew8xiD ziqUAK88TW6%9zXLhQpzIk8hk5njxdLpp2nV=yWR$XHrX#%{NJKA++Cd?$sskg>E>8DYlpc*J8VyoHRVrOJp0 zvhffymX<2x?RLWh*+@gi(z5Sm#M2~*LdMlnWyGT-h(gBIQf0)Wgz!Vg)q0ds6K2S` zTAF2a!wDHzOTUZ{I3eR|+1h2iTrTVNy5H~DYPDD_20#3Z#RAW`BhZqT(d_|Ofgtm+>Dx5CSbIV=9&M$pazKf~o#_AOu>#Znwin o{vgx(7)DEi(UM@aBz&pX4@Y3iAecJL&;S4c07*qoM6N<$g28DYmjD0& diff --git a/core/test/processing/visual/__screenshots__/shapes/contours-linux.png b/core/test/processing/visual/__screenshots__/shapes/contours-linux.png deleted file mode 100644 index 032c60a7538965af172c780806541a149a2616ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 991 zcmV<510ei~P)FV-VrRjE?m|K&N=lNCvJwkq!NS7l=H??wu~8IBzLI1oHtZ=2 zA7!cc{bg!8J$LT8Gk4~`dF%cb)7&#>&iQrDxzF=V!u78tK{osj;#yAlKea9{E;cqc z#>dCo+S*D=N)i(j{eFK_Q`5-E$m;4UPvr|9q0?gGw6wI+($enk?)mxo+uPgs_jj(` z(^6bqoRX5VwY4Q*^MbHiIOXKzoSvTk2~RK>%*)H8k9^rR)>?L)3_3bG0)c>h-!&Fm z_MPDC>#L}!XliOoj^Ylfg-u3AhU&z9Qc_Za0zcIb_xJainVCmNM`9>9NUg=i#j2_* z)hRnWdueG&?ZZzRdGzt|VRUP6Zyz2WR{OX_YW4Q^j*gC+S}crkGN94t^DQha7~Rg# z&pG?DlMAF)d3pKq@v*7JmWd7~H8nNq>FH+A{QUgogq}vTSo*fla-Z~+S=OG)KsqAi>G=g z`2PN`uC68`iWkFSS4#uuiM`V}89Y8d673ET59Rw-c%a1@S4%Vv{1rMoJMAO`544y} zEzvYQKR>gs6%`e7h!8x`A{MA6nx6jteogu}iQFS1#3y2boIecwo&S-RC}{N%X!stM zLxkXgmMH%<7$yJy{=OU{1P`>t$C3v7A4nc(5wXZE#BpM#6j3k=B%6hX2U@VZyGw2% zjz2v;Nu3{X%F4>JUubxv1>`$&3v-KtjC)#GC;Lv<9%>n|N5pX|3*ytw%}q~F51%2e zt*r+K2ePXckF^Ytpo}K3b9E|Y5ECu;#I>9d*K$H!%L#EUC&aa!@E6i*VdEdxhmimP N002ovPDHLkV1ic0*?ufv)TB3KD}PwXf*I`Lio{|&1SpZ?)`(u$48}7 zSt^zAAQJe|TCdj!rvsD8+3613xKMDStgUgLob5|Eigb%2bRkv zm}RqBJmCU((1NmKqa*YAT%k~u%Vj+0C%9@c%YKr6zYk`lYm=)MD*H()m5NrY?e%(i z@_D#wvCDpvcsy>gSYUIN}VPw7w}j8yKuosS1SxPB{g= z){kXp1NDa&K)2h)SuE(aE-5>kP$=Z_cyJaAdaVn~j@WLu8jYq{EaJGYP-nxh%-ENX!2G{9LV8Z*Om~=dG zo+3xW;jq{1bvPVyxm+X?nM@|X-w*pZsm&*3S_j~K^6>BgODV`87K`Qc`BtliM^izg k^$!rVND#D05VT163!sr))f)NWK>z>%07*qoM6N<$f@;K21ONa4 diff --git a/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png b/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png deleted file mode 100644 index aecfee50eac36f1b58ad3c38476e9db5d17ffb4c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 761 zcmVsCmYl~g(zLZCDB106h#EVABzwKiqzCngMpym*~vj@iBbenQjwss zDFRUu!^VmxrSFI2;{2T6*XN!aFsZ>OiH+uPfAyPZ-_9IeO4$H`>! z{lmw{$K~ZE$VDQN`}=!5bT!1$>UO&`r(Z^+Q6Lb|YPI=%9*h*eYA`9VKL}f?xdcDof z&1SQSLm7l?k(M2?ySsaJb%jG21ZoK=I|5G0a5#)}83bwxD?6J^CgXHEaV~=pEt<0b zlFep$E?`2mXv+S}$;nAD7{t*GLbNE$K4X7>ANC>~%|NV0Uv}W-(^-&d(U<-G{oQCZrcx;!Jr9`{W!b;JzHBxd+(YAB z4rE&JL36nrUTrntK@EhgR%@%(!nqvCw2H+d++ITZa5%)v@&YRG%ncLU?KTcw1eq29 zXEi+0E0xN0I{o_kir41=b&SX33WehA>OOQ5b`#tpJr4 r8@LYrVW9k9@IG&Kr2Et}jXCAmtp)3CXp=W5rH2&O)st6aw-ntFh|<1-xH+On2f zC!IO5=gIN-cy5lh6+IR$*K`e=1ft51b~t3*l~~NVP*u_{@b!GK_b+Ubm?d^tJY;_S V@wSBadBDJB@O1TaS?83{1OQeUr{Dkp diff --git a/core/test/processing/visual/__screenshots__/shapes/points-linux.png b/core/test/processing/visual/__screenshots__/shapes/points-linux.png deleted file mode 100644 index d0aecf8d308801172759a216d5c9a5373da55412..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AM?GB}Ln`9lPBr8^s=(tM`Oxmp z?Bp*IXFvUKVA-S^Z1aRyG;7H#)6z?yB7&Yt%PkRe&N5sgb~q@z=VJBxt;bBy75_Bw zUb{il{*lde{>@ji*8bfJVHv%hmbp#i$@>KtC)F4BPOGsqiCT8o=3jff}k2tuN|JJ}OAcZW4I z_w3my&R5R4$$T=uUm@gtBq2Qb46K#}t0lo|Nw8WH2(4r?x!rE@)I5Y%DwUc{CU|Nd zLaS6N_4|E1H4mZHYPFipCZ3vy&>9Q|<#HKMe_^#+;ZaZst@(VO&*$;<6^q3p7K=@% zQ~VGJLJMjbiA47MJ%0EJIK|^}xOh69-fTAbSq`BE78tDVnhG=J%& z7!HS;A)~dRjD!632plq&mMSBxDHe--Cq=N3v9wef@j@QKLdMckWyA~F1PB>R%PS*Z$VM76 zmX`A8smRCj{_#xwJ>6FnAGh|#X%`*Dogp8}DUq%m3$hcaL zb{Q|1%X+=;^?LPsJ&{O6qtWGZiFZ5@Xi3ZH_kf=i4u`YZY_(c#x7(x9=>4B{ffiXt z9S?*+>$!}7=z$PuK^Zfdj3Ez%Knu2-^FRo+fZc9~M`n;|{To(Gg4L2>wIqC~))x|j V=vi=ANS6Qr002ovPDHLkV1fs&1S0?d diff --git a/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png b/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png deleted file mode 100644 index a0836a3a6d019352d0e4cdb251912f5ea60cc2f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 405 zcmV;G0c!q&4g4&>0f#w$6U_Cg3LEz|{tiz%IAKeK|HKJ$5JV`R$kD@~6 zK|jfZKlly2mISXQ!D~tIS`x0brfG`f_;Hsgip&H)6yr+E@Or(PrunyoXnj7PvMhNH7{f5+dCn)EBhYHww(B~+5W{@Lx~}=ea|BvNQ7p^C z7h*i0&%W>Z#B)%sZQGJ0IgW!b1laf8+(g!OyV+M8e@Pp87PO&ld~^@v;jXS)EQtDwWP=Gd%bS)9F+w6t-F|yzBx;7Dy(O9*+ml ze$@GVb~qf-XcVvci92gB7$_8qUayBYJfvE!%H{HYzmM1a#GM67rIJ>w-S78!3DI`D zg}JbllS{^)_csuDdqp-n{#3&i9=uN|~k% zl8TrUY95u5RXR;f>dMswr{$jE&75=9VtLg{W|f%ExR7}Xhv2Sxc9)%kt2cMROeXot_v8w%{@Myh7VkmbX{603FQW>FVdQ&MBb@0K0oz0RR91 diff --git a/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png b/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png deleted file mode 100644 index 401b9974d104fb6c160742813712fd19b64d37b2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AOFdm2Ln`9lp4-UFY{=sr_}2br z?o@`ADq?0G{EFcprkZGMo)@mgdrxt*%*>}JbllS{^)_csuDdqp-n{#3&i9=uN|~k% zl8TrUY95u5RXR;f>dMswr{$jE&75=9VtLg{W|f%ExR7}Xhv2Sxc9)%kt2cMROeXot_v8w%{@Myh7VkmbX{603FQW>FVdQ&MBb@0K0oz0RR91 diff --git a/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png b/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png deleted file mode 100644 index c7c2b87e6474ded14d75d91e6e02c3c0fbaed4e9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 968 zcmV;(12_DMP)CCeOElKR*7$6(H?~GB4GN(kghT{|&XNd?C3LpN8X-bM1hG^KYvGGX6#f9A z&?txm5yJPxiJQs!V$3@;62*Or$(cL%p7Z3K-+kY){QZ?!^b5ZOQ%i!WCBf8^U}{M) zwIrBY5?raZySuAjaulbuwzjqw7Z-badTMKH)6&vBJw20?lPfDLJ32b%=jYef*7OJL z#&NCj@o_&tzqq)#rlzK`v9Z(BQ;WsICu6Q;XJ>nPdCkns=#SZlgIa7xXlQ7DetunD z-TNnK8RL?YlB}$(n3$OT{eAsmJ8(oxIkDdL^>t8C(An9UrnR!N5*8MAe}9j3Sy`F> zyd8+OCMPGAlQL*%XecNsu-1Bed&|kmnVOnn{P6IQn3&kz-L22^9Wt%$?QIVakG#A* zc0#E^ zR5&%XIy*ZH3kxqUE=o&FBO)S}mzVkV@bC~neJ-2Gv}$T<$d)=j= ziH(hoii&D!X(8{;&CPdrcgCI65xn*3zDA~%o}PYka$+p@{*f(eDJ6=8)z{bO=H{lR zrXC+3Ti*jiLqnXVKHb*{wCF7s=U81`)hGP|ffl5sr2M~IZEbD5o@!w?H#g34c6QeK z9vB!H_^I83ii!#&ThyoN>1p~8*{5dmZlXo1GBY#zqyb!BUK(0N{s*(w)zzim#J#<} z@bGYNZ*RhWb8|zJrv0JCOGnPm&(DcGRhRp@`QYFnIyzdP%O)}{N)bgXBO}Az-JPU! zf~-oFPfAL9|177J2KANT6Ms4*d)eCBs?TK;nHJ5C^zhQr4b|!F?CcP%tE($R>-P4R zU1akZzr4IuS68!t_J>^|(*o8bDD+_Z9jgWe1W>4yqSAtii3y@c=i%igc_Sku`Yhie z)&lx8=R)JFs;Z)jDiv!jptMrtaN-rwpSJ@?v=sa)CF!*7qoX5Diz@_<;~pIy)gQAD z$F*Q%V}qi_nbKS6+-UXp_w$waj+2$<$r`7$3@BhlMMY@w$yoncj%&0O&TNl{E45sR qsU^YGl3;2{FtsF@S`thx3I70rNwxo+`ty7M0000F0UQ`JFS|tM2oe-uJvY&&Rh<^4iE(VuJ_Yfu$vbr6q%VzF?! zTsC=pK3}m|)HJP9scg4fcrOB;7Nt-q)a&(MGOX9@Xf&EgB(m9TKA(p-;t*)j!MtAY z>2!h*H{yQ3x7%$x)nG7qKA-ee9*>9K053%#&?4x;^pTFoqkdSeR@DX*YY7JfBe8!{ z9!#u7xPuvv#G*DBO3QFC&PXh3gQ2v@$6&&dSQG}sYUzU^M`BSJ468-{4A$v%@FTI% z!P4pUbUKBXnt;`!AJ)xg)9G}=Pjm$IRhdkN{?KSN;JqebwJ4*}C?1c)9YkEO*K)b+ za5zGt5WLYG2`&0wyWK8)ESJOKkW401sZ=hPE0s#{Msp;zVzJnAxr9$PXFi_?0)bYm z^>{oYk;rg3gg2Ul)!OZLZnyh(yTOMW@p`>}et!IZ|70?uzg{jE`b}iB*}zN9!D`{d zddCzD2KW2@`(!qog~MTZBNwa|GAuo&^ZBeF(oYAeun1lY6BZj2doCLmL23!Z0x_{D zghh~A{IEV_Vo?Z-AhkGQ8OFq-5EemdnT2H*6N^Gv1fyjb7AGbawXg_A>oY8VOe|_) z5sVfHOBfT2T37_5#fF86iA60eBGl5uLdL|R92OC3y~D!C#G)J)5o*z}dc7V#CKlze zh)}Cmt4YPgq8t|CYxVnmyWI}InWND(O)V_K*P{QZjK^bm`d8p-{X;A*87wUsEG-$n aRO>fp*ohoT3uylU0000y14WAkMT-SRi-rGCYcLqJS}lB47G7yh zr&Fy~8wdod)#_|E!%u&bS6YDE?M^0>u~U@!08fWb+fy z%49O&aX1{VH{|gX(5lz#KA*4K?cTNI@e|O37`dG=#MvMyL#& z_22JdkAqy&VuNR0da{=P@P6JfX8`ThP#B(hj6@Oe=PYjOW#+-x>v zkB?4-^ZA@kr#&9eWHQ0$*`d;c82kM`IKe0PQ>Q!dr)ITU;oTL#1S3>h5M!xSf(@EX zrWa0x(P$(+0xB&MBaGZ`x7gwt(j%bKA~E9OFOe7ll@^Kda=GB)FOd=fl@@o45l^N- zTm-aQBt|@w4iOR1YLOW6Kx$Ycpw+s^C>o{+XthMd_y~Ojv|8d~duCjc43o&|-~|?*YG(TCH|C9PxNOm&<_=+gPNV3|hcywZfM^1Dlp?C|WEiS}Z79EPScfFMOS> UYk7IszyJUM07*qoM6N<$g7s=T0RR91 diff --git a/core/test/processing/visual/src/core/VisualTestRunner.java b/core/test/processing/visual/src/core/VisualTestRunner.java index 7fd3a82a74..758ff0ec30 100644 --- a/core/test/processing/visual/src/core/VisualTestRunner.java +++ b/core/test/processing/visual/src/core/VisualTestRunner.java @@ -198,6 +198,7 @@ public SketchRunner(ProcessingSketch userSketch, TestConfig config) { public void settings() { size(config.width, config.height); + pixelDensity(1); } public void setup() { From fd72f79c36dcf1cc23e6ead3ca52f85045d02e6d Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Wed, 22 Oct 2025 00:44:53 +0530 Subject: [PATCH 26/29] Revert "fixed the pixeldensity to 1" This reverts commit 66071ac191d24a4a25d8d8257f58cbeb957c6f35. --- .../rendering/linear-gradient-linux.png | Bin 0 -> 2943 bytes .../shapes-3d/per-vertex-fills-linux.png | Bin 0 -> 661 bytes .../shapes-3d/per-vertex-strokes-linux.png | Bin 0 -> 696 bytes .../shapes-3d/vertex-coordinates-linux.png | Bin 0 -> 134 bytes .../shapes/bezier-curves-linux.png | Bin 0 -> 722 bytes .../shapes/closed-curves-linux.png | Bin 0 -> 723 bytes .../shapes/closed-polylines-linux.png | Bin 0 -> 602 bytes .../__screenshots__/shapes/contours-linux.png | Bin 0 -> 991 bytes .../__screenshots__/shapes/curves-linux.png | Bin 0 -> 702 bytes .../shapes/curves-tightness-linux.png | Bin 0 -> 761 bytes .../__screenshots__/shapes/lines-linux.png | Bin 0 -> 387 bytes .../__screenshots__/shapes/points-linux.png | Bin 0 -> 254 bytes .../__screenshots__/shapes/polylines-linux.png | Bin 0 -> 583 bytes .../__screenshots__/shapes/quad-strips-linux.png | Bin 0 -> 405 bytes .../shapes/quadratic-beziers-linux.png | Bin 0 -> 579 bytes .../__screenshots__/shapes/quads-linux.png | Bin 0 -> 188 bytes .../shapes/single-closed-contour-linux.png | Bin 0 -> 222 bytes .../shapes/single-unclosed-contour-linux.png | Bin 0 -> 222 bytes .../shapes/triangle-fans-linux.png | Bin 0 -> 968 bytes .../shapes/triangle-strips-linux.png | Bin 0 -> 692 bytes .../__screenshots__/shapes/triangles-linux.png | Bin 0 -> 686 bytes .../visual/src/core/VisualTestRunner.java | 1 - 22 files changed, 1 deletion(-) create mode 100644 core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-fills-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/per-vertex-strokes-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/closed-curves-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/closed-polylines-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/contours-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/curves-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/lines-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/points-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/polylines-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/quadratic-beziers-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/quads-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/single-closed-contour-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/triangle-strips-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shapes/triangles-linux.png diff --git a/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png b/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..87bb74dfc26bbc8a7eb9abf7333500b87bec0a24 GIT binary patch literal 2943 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png b/core/test/processing/visual/__screenshots__/shapes-3d/vertex-coordinates-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e07fe529c8ee84295a8d219564ef7954d4f5ca85 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AzMd|QAr*0N&n;wRFyLv}@UQ;n zxmoMGS1Sk|-7`6@UHX<#Zs#_a=ta60r)79=QN_=-JNe?t>YUel7l8&dc)I$ztaD0e F0suSKG4B8X literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png b/core/test/processing/visual/__screenshots__/shapes/bezier-curves-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e628f405416f0c1608764e2352c817234fcbda87 GIT binary patch literal 722 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0FXZV4CUa;uum9_ja1CM^T`}@!Kvg zAC*NG^3QQmWD>o2^q{7JifdGei%67|NDRx-g^I<21qFIXjyeiVj4CO(P^R7~;J876 zM`+fn?+eA_*7aZAdHgXq-*@xQIluq^e^4<0cg|O{NxFZIrzvDygd>LwmtKFBmzSUa z`q|&Vwe|J#(+d?cI*&fuzTJGzsWUZp;+-x#dYB!(7Y3Y3GX{!${=7Lv>u9PBN0ha- z_2P>h%Z-dBcub^t^&UH1Fq(b#`}gk_Q)h;)PBoD#lw@zZcJX51+OWeb&&0&XTg&jp z9BW)qT2@w8TG|>s(^GA7qXPrTy4bilwr0mUr+RL`^;>=!WZmkZl@dH`b561GIL-A7 z*P9M9D{TAi(3K&RPqL&NfB5l3WBO@DNswknz3H!?J!4BZ4$u&p99wC{@i)~`C*f4 zMP}&g(5+Et{}sygANN`+bgAH=(4H*~moHy-QEIH#;A}rE*U#==&mO<__U-5&J2utG z&p!L?+c&c(4_>@~u=?unD%C_DzvaO(Cz>A?aGd5)d2m5~!3CxVVsFZRzkaQ~?(fv) z%a#w&)t^M=db^m>P3kwTN%b&GtlwWMAicg#9;j;Sbwb!L< zHgM>?`B@{kOk~dTqE~Ade4U=OamV)U?BZPamp)DX|MV#<-wdB+ckjmjXq10;uq(=8 z`Q^r~$I~_+4CA}b2$i*)F$H}hIMP!eeK`9dw0-Emfyi9QoY9? v?~GY@G2?;rlr@g8>*1*anXzGER6WDBfW`0DPwz1Vrd0+{S3j3^P6(U7{_NTQ6oai*oln=O{SDA$ZGb;ia!APv6{)kb_UU;M6xr}(5B0n^wQyzr4H*27|oF@urnZrEYI;eLkOFudmnZ_%KlxAElDaMfa${V0hnD3Yivl*%ymNGMUt9G&Y;<`S}^=vLMqU zE&F=CE|<$LmrE*@!Z!#06yd}Z5NN@EhO)!S>Fw?ZS_o!>9QvcD1wAAAQ!OM=moV6-F{EeV8HqtRHcR`}F^2(3gS(d+f_ zss9jK#bPm^&*M}7A+(y!W-u81{2mFd!C+un7ITlw*KJt`-#aU7>!ub0bZ{DvG_ z3&xa6rN8#*b^@-~D_o<~>EQQ-&|0vXR;v{bhoAR|oD_${K|f@)7L>7CtriLew8xiD ziqUAK88TW6%9zXLhQpzIk8hk5njxdLpp2nV=yWR$XHrX#%{NJKA++Cd?$sskg>E>8DYlpc*J8VyoHRVrOJp0 zvhffymX<2x?RLWh*+@gi(z5Sm#M2~*LdMlnWyGT-h(gBIQf0)Wgz!Vg)q0ds6K2S` zTAF2a!wDHzOTUZ{I3eR|+1h2iTrTVNy5H~DYPDD_20#3Z#RAW`BhZqT(d_|Ofgtm+>Dx5CSbIV=9&M$pazKf~o#_AOu>#Znwin o{vgx(7)DEi(UM@aBz&pX4@Y3iAecJL&;S4c07*qoM6N<$g28DYmjD0& literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/contours-linux.png b/core/test/processing/visual/__screenshots__/shapes/contours-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..032c60a7538965af172c780806541a149a2616ee GIT binary patch literal 991 zcmV<510ei~P)FV-VrRjE?m|K&N=lNCvJwkq!NS7l=H??wu~8IBzLI1oHtZ=2 zA7!cc{bg!8J$LT8Gk4~`dF%cb)7&#>&iQrDxzF=V!u78tK{osj;#yAlKea9{E;cqc z#>dCo+S*D=N)i(j{eFK_Q`5-E$m;4UPvr|9q0?gGw6wI+($enk?)mxo+uPgs_jj(` z(^6bqoRX5VwY4Q*^MbHiIOXKzoSvTk2~RK>%*)H8k9^rR)>?L)3_3bG0)c>h-!&Fm z_MPDC>#L}!XliOoj^Ylfg-u3AhU&z9Qc_Za0zcIb_xJainVCmNM`9>9NUg=i#j2_* z)hRnWdueG&?ZZzRdGzt|VRUP6Zyz2WR{OX_YW4Q^j*gC+S}crkGN94t^DQha7~Rg# z&pG?DlMAF)d3pKq@v*7JmWd7~H8nNq>FH+A{QUgogq}vTSo*fla-Z~+S=OG)KsqAi>G=g z`2PN`uC68`iWkFSS4#uuiM`V}89Y8d673ET59Rw-c%a1@S4%Vv{1rMoJMAO`544y} zEzvYQKR>gs6%`e7h!8x`A{MA6nx6jteogu}iQFS1#3y2boIecwo&S-RC}{N%X!stM zLxkXgmMH%<7$yJy{=OU{1P`>t$C3v7A4nc(5wXZE#BpM#6j3k=B%6hX2U@VZyGw2% zjz2v;Nu3{X%F4>JUubxv1>`$&3v-KtjC)#GC;Lv<9%>n|N5pX|3*ytw%}q~F51%2e zt*r+K2ePXckF^Ytpo}K3b9E|Y5ECu;#I>9d*K$H!%L#EUC&aa!@E6i*VdEdxhmimP N002ovPDHLkV1ic0*?ufv)TB3KD}PwXf*I`Lio{|&1SpZ?)`(u$48}7 zSt^zAAQJe|TCdj!rvsD8+3613xKMDStgUgLob5|Eigb%2bRkv zm}RqBJmCU((1NmKqa*YAT%k~u%Vj+0C%9@c%YKr6zYk`lYm=)MD*H()m5NrY?e%(i z@_D#wvCDpvcsy>gSYUIN}VPw7w}j8yKuosS1SxPB{g= z){kXp1NDa&K)2h)SuE(aE-5>kP$=Z_cyJaAdaVn~j@WLu8jYq{EaJGYP-nxh%-ENX!2G{9LV8Z*Om~=dG zo+3xW;jq{1bvPVyxm+X?nM@|X-w*pZsm&*3S_j~K^6>BgODV`87K`Qc`BtliM^izg k^$!rVND#D05VT163!sr))f)NWK>z>%07*qoM6N<$f@;K21ONa4 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png b/core/test/processing/visual/__screenshots__/shapes/curves-tightness-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..aecfee50eac36f1b58ad3c38476e9db5d17ffb4c GIT binary patch literal 761 zcmVsCmYl~g(zLZCDB106h#EVABzwKiqzCngMpym*~vj@iBbenQjwss zDFRUu!^VmxrSFI2;{2T6*XN!aFsZ>OiH+uPfAyPZ-_9IeO4$H`>! z{lmw{$K~ZE$VDQN`}=!5bT!1$>UO&`r(Z^+Q6Lb|YPI=%9*h*eYA`9VKL}f?xdcDof z&1SQSLm7l?k(M2?ySsaJb%jG21ZoK=I|5G0a5#)}83bwxD?6J^CgXHEaV~=pEt<0b zlFep$E?`2mXv+S}$;nAD7{t*GLbNE$K4X7>ANC>~%|NV0Uv}W-(^-&d(U<-G{oQCZrcx;!Jr9`{W!b;JzHBxd+(YAB z4rE&JL36nrUTrntK@EhgR%@%(!nqvCw2H+d++ITZa5%)v@&YRG%ncLU?KTcw1eq29 zXEi+0E0xN0I{o_kir41=b&SX33WehA>OOQ5b`#tpJr4 r8@LYrVW9k9@IG&Kr2Et}jXCAmtp)3CXp=W5rH2&O)st6aw-ntFh|<1-xH+On2f zC!IO5=gIN-cy5lh6+IR$*K`e=1ft51b~t3*l~~NVP*u_{@b!GK_b+Ubm?d^tJY;_S V@wSBadBDJB@O1TaS?83{1OQeUr{Dkp literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/points-linux.png b/core/test/processing/visual/__screenshots__/shapes/points-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d0aecf8d308801172759a216d5c9a5373da55412 GIT binary patch literal 254 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AM?GB}Ln`9lPBr8^s=(tM`Oxmp z?Bp*IXFvUKVA-S^Z1aRyG;7H#)6z?yB7&Yt%PkRe&N5sgb~q@z=VJBxt;bBy75_Bw zUb{il{*lde{>@ji*8bfJVHv%hmbp#i$@>KtC)F4BPOGsqiCT8o=3jff}k2tuN|JJ}OAcZW4I z_w3my&R5R4$$T=uUm@gtBq2Qb46K#}t0lo|Nw8WH2(4r?x!rE@)I5Y%DwUc{CU|Nd zLaS6N_4|E1H4mZHYPFipCZ3vy&>9Q|<#HKMe_^#+;ZaZst@(VO&*$;<6^q3p7K=@% zQ~VGJLJMjbiA47MJ%0EJIK|^}xOh69-fTAbSq`BE78tDVnhG=J%& z7!HS;A)~dRjD!632plq&mMSBxDHe--Cq=N3v9wef@j@QKLdMckWyA~F1PB>R%PS*Z$VM76 zmX`A8smRCj{_#xwJ>6FnAGh|#X%`*Dogp8}DUq%m3$hcaL zb{Q|1%X+=;^?LPsJ&{O6qtWGZiFZ5@Xi3ZH_kf=i4u`YZY_(c#x7(x9=>4B{ffiXt z9S?*+>$!}7=z$PuK^Zfdj3Ez%Knu2-^FRo+fZc9~M`n;|{To(Gg4L2>wIqC~))x|j V=vi=ANS6Qr002ovPDHLkV1fs&1S0?d literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png b/core/test/processing/visual/__screenshots__/shapes/quad-strips-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a0836a3a6d019352d0e4cdb251912f5ea60cc2f7 GIT binary patch literal 405 zcmV;G0c!q&4g4&>0f#w$6U_Cg3LEz|{tiz%IAKeK|HKJ$5JV`R$kD@~6 zK|jfZKlly2mISXQ!D~tIS`x0brfG`f_;Hsgip&H)6yr+E@Or(PrunyoXnj7PvMhNH7{f5+dCn)EBhYHww(B~+5W{@Lx~}=ea|BvNQ7p^C z7h*i0&%W>Z#B)%sZQGJ0IgW!b1laf8+(g!OyV+M8e@Pp87PO&ld~^@v;jXS)EQtDwWP=Gd%bS)9F+w6t-F|yzBx;7Dy(O9*+ml ze$@GVb~qf-XcVvci92gB7$_8qUayBYJfvE!%H{HYzmM1a#GM67rIJ>w-S78!3DI`D zg}JbllS{^)_csuDdqp-n{#3&i9=uN|~k% zl8TrUY95u5RXR;f>dMswr{$jE&75=9VtLg{W|f%ExR7}Xhv2Sxc9)%kt2cMROeXot_v8w%{@Myh7VkmbX{603FQW>FVdQ&MBb@0K0oz0RR91 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png b/core/test/processing/visual/__screenshots__/shapes/single-unclosed-contour-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..401b9974d104fb6c160742813712fd19b64d37b2 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~AOFdm2Ln`9lp4-UFY{=sr_}2br z?o@`ADq?0G{EFcprkZGMo)@mgdrxt*%*>}JbllS{^)_csuDdqp-n{#3&i9=uN|~k% zl8TrUY95u5RXR;f>dMswr{$jE&75=9VtLg{W|f%ExR7}Xhv2Sxc9)%kt2cMROeXot_v8w%{@Myh7VkmbX{603FQW>FVdQ&MBb@0K0oz0RR91 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png b/core/test/processing/visual/__screenshots__/shapes/triangle-fans-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c2b87e6474ded14d75d91e6e02c3c0fbaed4e9 GIT binary patch literal 968 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 UYk7IszyJUM07*qoM6N<$g7s=T0RR91 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/src/core/VisualTestRunner.java b/core/test/processing/visual/src/core/VisualTestRunner.java index 758ff0ec30..7fd3a82a74 100644 --- a/core/test/processing/visual/src/core/VisualTestRunner.java +++ b/core/test/processing/visual/src/core/VisualTestRunner.java @@ -198,7 +198,6 @@ public SketchRunner(ProcessingSketch userSketch, TestConfig config) { public void settings() { size(config.width, config.height); - pixelDensity(1); } public void setup() { From a342a775fb0a9a82a1b0470448faf81e9fef02af Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Wed, 22 Oct 2025 00:47:13 +0530 Subject: [PATCH 27/29] fixed pixeldensity to 1 --- core/test/processing/visual/src/core/VisualTestRunner.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/test/processing/visual/src/core/VisualTestRunner.java b/core/test/processing/visual/src/core/VisualTestRunner.java index 7fd3a82a74..758ff0ec30 100644 --- a/core/test/processing/visual/src/core/VisualTestRunner.java +++ b/core/test/processing/visual/src/core/VisualTestRunner.java @@ -198,6 +198,7 @@ public SketchRunner(ProcessingSketch userSketch, TestConfig config) { public void settings() { size(config.width, config.height); + pixelDensity(1); } public void setup() { From 4c190b36066f60f25d0b8014b382736a3b93d989 Mon Sep 17 00:00:00 2001 From: Vaivaswat Dubey <113991324+Vaivaswat2244@users.noreply.github.com> Date: Wed, 22 Oct 2025 11:25:24 +0530 Subject: [PATCH 28/29] Configure dependencyUpdates task in build.gradle.kts Add configuration for dependencyUpdates task to manage non-stable versions. --- build.gradle.kts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index bdf851ecfb..f74e28f723 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -12,3 +12,20 @@ plugins { // Can be deleted after the migration to Gradle is complete layout.buildDirectory = file(".build") + +// Configure the dependencyUpdates task +tasks { + dependencyUpdates { + gradleReleaseChannel = "current" + + val nonStableKeywords = listOf("alpha", "beta", "rc") + + fun isNonStable(version: String) = nonStableKeywords.any { + version.lowercase().contains(it) + } + + rejectVersionIf { + isNonStable(candidate.version) && !isNonStable(currentVersion) + } + } +} From 60af8627740039dafa1745727b3e32dabf1f5b21 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Thu, 23 Oct 2025 21:37:40 +0530 Subject: [PATCH 29/29] removing rendering gradient screenshot --- .../rendering/linear-gradient-linux.png | Bin 2943 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png diff --git a/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png b/core/test/processing/visual/__screenshots__/rendering/linear-gradient-linux.png deleted file mode 100644 index 87bb74dfc26bbc8a7eb9abf7333500b87bec0a24..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2943 zcmX|DYgCh07X5gGlvinWKoF%B%OFru&?pEhh)gx8ASq!13ItjrVu`3pggAwTIyj<& z*aQe_tC;ed6$})H;Hp?uB%mW920|Q%m*rW3AXFZ6K6Bt_Zoa$sKKtx@?!B;|8fdo6 zb{RnsW-dBTTy6O>8Z%TG?M zPaaPr-Z)1qrlSXph-bORI^QH&zNXErAtpcHNzk9tJb%4DV+H%%nGf8@V{xB*;;ps` zYuHz;cv#H%2v^$@uLe(qkgFX(7564Ap*@fEhfIgeL0E$uo!oN zeGVOYV*?89K39kK(6PSF#Iq1}rRbIHhb6RaHl_sU<26KipH$F4a?yFX9umCr86 z)gr;M6X`~46=&gu}H<~M@3nprFDs+ptRHD4XS;8y`#;zFBr&I-rl z&DgFzgkLcG$9Rq>&+x3!-`J1Imd2;A<-y+`KgY8vJOf-Uk56;v>A|K%*;LHDIpK4` z@@$8pRl{W-32-}}w|zk$KdtRQ?Y5wcKScd!ycPg@JVvJ3JFlnRH@;IgcVZdAvGt!0 zZCYe#=x2NMfIfCbf0|>Do?oz|{tbzD#DtH~gt)QJO7|IED4KD2nh7QeD-2f2{L#8# z`S&?-F~pm1EnheC_0fSR^fx#6E(2dK*#3Fg33Pubt$um>>fA--uxwSaGjXHDo1U${ z?yRGwSS9*xR2E7+d$IZf*TWMV*(jOr<{N^YyCe@I`HR;7N*{0C3)YiHjQhE!2Hr6` zD@@_Jrg7s5EnjVAey!_9UBBQA<7Am7_MgjCMtE35D!7TY1sgXaMYm*jAKw7QSI}QK zwUFXl>2I@I47C&~f#sV#sz7{(ab=qw)?TA%Rxqd#rW*NQ74dFJ5y6x*JvJkM?&$nXY)MQ`MrJnd zj{5?H4>4cfwjt%!(Fd-s{|MzJm2w2rI+Mi7d2N&Jyj^$CjZlf0#b+mZn}eKH?F$?nybc~M-Rf}1u63WWSV%!FIH zto89YP`HU?!EMWsVufOP2_pcJvlE|&RDDcLZaV)*UfH7DBT1<#VSt>nBPs1*7-o=i zznZe46f#JutoB(`ioO&^blDesYWX@>Kj;xVfeEa{_pw#WQLa+bxFLKccpNV2d2X}V zu=tS^p&cZ}?vXQb9g7swEQc$aC9uY-?Ev;7@p_|J2f;*=p_@ue0CSdftx+0d!+JaC z{(BU}_x;q_bK|HknjSO4KJ10a8O9cm!|y zE+8Xg@VvcvCD#A<)ZQtjJ=Cw0+I~{0r`5w)*vjb|HX#G6a^j+%VcMgkMN-V#RC#Po z^LCIiCF-eeX+RlV#oLHDUm)hnO$ritNbEE8P?kcFi8%hk&RFZEII!qmkU5SmrIf1P zVg$OCK{H>l61Kd>(5gBJ~RNe!TjM+bW!fW*4S=4kV_5+ zaY#`u^%5Y9V~af32V$y*_a{^q#lmSj8a+^UEf%Y&E@dfNzWXqLJX5^Uk?K_SAO*%b z*%(H_R-U8~G&6?k4$BMmk>XI~FMp%Lm=!Z3@1;=j#AF!LnnmD9_1$b*%eUxAfnu#o z?+G+QHu#@DmLYUtAM;@f#TqGc>y9c54S=Gj?hL;W?i}r`cPnC%azknBudz_BSDDN8 z7B(WUY(Ml>?-}rVNFmQ2&_xQ(py0O@xM1^AhdKjTaJiBUYg&t#*sw5BSAVkvMY||QSp8c;w2R!H)yvU}_Bwxj>LOfc z;+Sqq#4()a;WtMGo|{REhEd_#%}BAH_O7L{0PV;x=O#3}g7AFN`>fuuC9ozH%ml{o zb-XMU-kjo4pD)u!0a+a9vn6sAug>sWs{~%B7LlVYf^+{3;@5}e>@DIhjSNwWJ2(%Ae}j8V=Jl{X|s8;~#|wA6zp zoi6sqi{2waIyvTT3SVC0%tW)zQP~uyub7RG6-G5jUGY zx78@>acFz6r1RJ7VfHcZaC+Lm=SnWZfitw4-%yFQ zw(L)yTayeYnctwEPgA2W{0j9<