From 2e9db0e9d3753aa9733d3af5eb4c5c5ba75c1caa Mon Sep 17 00:00:00 2001 From: Stef Tervelde Date: Tue, 25 Mar 2025 09:51:28 +0100 Subject: [PATCH 01/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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/37] 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 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 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/37] 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/37] 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/37] 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/37] 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< Date: Fri, 24 Oct 2025 22:07:41 +0530 Subject: [PATCH 30/37] added font alignment size leading and width tests --- .../src/test/typography/TypographyTest.java | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 core/test/processing/visual/src/test/typography/TypographyTest.java diff --git a/core/test/processing/visual/src/test/typography/TypographyTest.java b/core/test/processing/visual/src/test/typography/TypographyTest.java new file mode 100644 index 0000000000..b4ad54fb6a --- /dev/null +++ b/core/test/processing/visual/src/test/typography/TypographyTest.java @@ -0,0 +1,335 @@ +package processing.visual.src.test.typography; + +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("typography") +@Tag("text") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class TypographyTest extends VisualTest { + + @Nested + @Tag("font") + @DisplayName("textFont Tests") + class TextFontTests { + + @Test + @Order(1) + @DisplayName("Default font rendering") + public void testDefaultFont() { + assertVisualMatch("typography/font/default-font", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + PFont font = p.createFont("SansSerif", 20); + p.textFont(font); + p.textSize(20); + p.textAlign(PApplet.LEFT, PApplet.BASELINE); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.fill(0); // ← Must be in draw(), not just setup() + p.text("test", 5, 25); // ← Move away from edge + } + }, new TestConfig(50, 50)); + } + + @Test + @Order(2) + @DisplayName("Monospace font rendering") + public void testMonospaceFont() { + assertVisualMatch("typography/font/monospace-font", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + PFont mono = p.createFont("Monospaced", 20); + p.textFont(mono); + p.textAlign(PApplet.LEFT, PApplet.BASELINE); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.fill(0); // ← Add this + p.text("test", 5, 25); + } + }, new TestConfig(50, 50)); + } + + @Test + @Order(3) + @DisplayName("System font rendering") + public void testSystemFont() { + assertVisualMatch("typography/font/system-font", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + PFont font = p.createFont("Serif", 32); + p.textFont(font); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.fill(0); // ← Add this + p.text("test", 10, 50); // ← Better positioning + } + }, new TestConfig(100, 100)); + } + } + + + @Nested + @Tag("alignment") + @DisplayName("textAlign Tests") + class TextAlignTests { + + @Test + @DisplayName("All horizontal and vertical alignments with single word") + public void testAllAlignmentsSingleWord() { + int[][] alignments = { + {PApplet.LEFT, PApplet.TOP}, + {PApplet.CENTER, PApplet.TOP}, + {PApplet.RIGHT, PApplet.TOP}, + {PApplet.LEFT, PApplet.CENTER}, + {PApplet.CENTER, PApplet.CENTER}, + {PApplet.RIGHT, PApplet.CENTER}, + {PApplet.LEFT, PApplet.BOTTOM}, + {PApplet.CENTER, PApplet.BOTTOM}, + {PApplet.RIGHT, PApplet.BOTTOM} + }; + + TestConfig config = new TestConfig(300, 300); + + for (int[] alignment : alignments) { + final int alignX = alignment[0]; + final int alignY = alignment[1]; + final String alignName = getAlignmentName(alignX, alignY); + + assertVisualMatch("typography/align/single-word-" + alignName, + new ProcessingSketch() { + PFont font; + + @Override + public void setup(PApplet p) { + font = p.createFont("SansSerif", 60); + p.textFont(font); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.textAlign(alignX, alignY); + p.fill(0); // ← Must be in draw() + p.text("Single Line", p.width / 2, p.height / 2); + + // Draw bounding box + p.noFill(); + p.stroke(255, 0, 0); + p.strokeWeight(2); + + float tw = p.textWidth("Single Line"); + float th = p.textAscent() + p.textDescent(); + float x = calculateX(p, alignX, p.width / 2f, tw); + float y = calculateY(p, alignY, p.height / 2f, th); + p.rect(x, y, tw, th); + } + }, config); + } + } + + @Test + @DisplayName("Multi-line text with manual line breaks") + public void testMultiLineManualText() { + int[][] alignments = { + {PApplet.LEFT, PApplet.TOP}, + {PApplet.CENTER, PApplet.TOP}, + {PApplet.RIGHT, PApplet.TOP}, + {PApplet.LEFT, PApplet.CENTER}, + {PApplet.CENTER, PApplet.CENTER}, + {PApplet.RIGHT, PApplet.CENTER}, + {PApplet.LEFT, PApplet.BOTTOM}, + {PApplet.CENTER, PApplet.BOTTOM}, + {PApplet.RIGHT, PApplet.BOTTOM} + }; + + TestConfig config = new TestConfig(150, 100); + + for (int[] alignment : alignments) { + final int alignX = alignment[0]; + final int alignY = alignment[1]; + final String alignName = getAlignmentName(alignX, alignY); + + assertVisualMatch("typography/align/multi-line-" + alignName, + new ProcessingSketch() { + PFont font; + + @Override + public void setup(PApplet p) { + font = p.createFont("SansSerif", 12); + p.textFont(font); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + float xPos = 20; + float yPos = 20; + float boxWidth = 100; + float boxHeight = 60; + + // Draw box + p.noFill(); + p.stroke(200); + p.strokeWeight(2); + p.rect(xPos, yPos, boxWidth, boxHeight); + + // Draw text + p.fill(0); // ← Must be before text + p.noStroke(); + p.textAlign(alignX, alignY); + p.text("Line 1\nLine 2\nLine 3", xPos, yPos, boxWidth, boxHeight); + + // Draw bounding box (optional) + p.noFill(); + p.stroke(255, 0, 0); + p.strokeWeight(1); + } + }, config); + } + } + + // Helper methods + private String getAlignmentName(int alignX, int alignY) { + String x = alignX == PApplet.LEFT ? "left" : + alignX == PApplet.CENTER ? "center" : "right"; + String y = alignY == PApplet.TOP ? "top" : + alignY == PApplet.CENTER ? "center" : "bottom"; + return x + "-" + y; + } + + private float calculateX(PApplet p, int alignX, float x, float tw) { + if (alignX == PApplet.LEFT) return x; + if (alignX == PApplet.CENTER) return x - tw / 2; + return x - tw; + } + + private float calculateY(PApplet p, int alignY, float y, float th) { + if (alignY == PApplet.TOP) return y; + if (alignY == PApplet.CENTER) return y - th / 2; + return y - th; + } + } + + + @Nested + @Tag("size") + @DisplayName("textSize Tests") + class TextSizeTests { + + @Test + @DisplayName("Text sizes comparison") + public void testTextSizes() { + assertVisualMatch("typography/size/sizes-comparison", new ProcessingSketch() { + PFont font; + + @Override + public void setup(PApplet p) { + font = p.createFont("SansSerif", 12); + p.textFont(font); + p.textAlign(PApplet.LEFT, PApplet.BASELINE); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.fill(0); // ← Add this + + int[] sizes = {12, 16, 20, 24, 30}; + float yOffset = 20; + + for (int size : sizes) { + p.textSize(size); + p.text("Size: " + size + "px", 10, yOffset); + yOffset += size + 5; + } + } + }, new TestConfig(300, 200)); + } + } + + @Nested + @Tag("leading") + @DisplayName("textLeading Tests") + class TextLeadingTests { + + @Test + @DisplayName("Text leading with different values") + public void testTextLeading() { + assertVisualMatch("typography/leading/different-values", new ProcessingSketch() { + PFont font; + + @Override + public void setup(PApplet p) { + font = p.createFont("SansSerif", 16); + p.textFont(font); + p.textSize(16); + p.textAlign(PApplet.LEFT, PApplet.BASELINE); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.fill(0); // ← Add this + + int[] leadingValues = {10, 20, 30}; + float yOffset = 25; + + for (int leading : leadingValues) { + p.textLeading(leading); + p.text("Leading: " + leading, 10, yOffset); + yOffset += 25; + p.text("Line 1\nLine 2", 10, yOffset); + yOffset += leading * 2 + 15; + } + } + }, new TestConfig(300, 250)); + } + } + + + @Nested + @Tag("width") + @DisplayName("textWidth Tests") + class TextWidthTests { + + @Test + @DisplayName("Verify width of a string") + public void testTextWidth() { + assertVisualMatch("typography/width/string-width", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.textSize(20); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + String text = "Width Test"; + float width = p.textWidth(text); + + p.fill(0); + p.text(text, 0, 30); + + p.noFill(); + p.stroke(255, 0, 0); + p.rect(0, 10, width, 20); + } + }, new TestConfig(100, 100)); + } + } +} \ No newline at end of file From 8c7c717fea20cc201018afcce347eb33544a9c0b Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Fri, 24 Oct 2025 22:09:37 +0530 Subject: [PATCH 31/37] added complex pfont tests --- .../src/test/typography/TypographyTest.java | 164 ++++++++++++++++++ 1 file changed, 164 insertions(+) diff --git a/core/test/processing/visual/src/test/typography/TypographyTest.java b/core/test/processing/visual/src/test/typography/TypographyTest.java index b4ad54fb6a..52f968639a 100644 --- a/core/test/processing/visual/src/test/typography/TypographyTest.java +++ b/core/test/processing/visual/src/test/typography/TypographyTest.java @@ -332,4 +332,168 @@ public void draw(PApplet p) { }, new TestConfig(100, 100)); } } + + @Nested + @Tag("pfont") + @DisplayName("PFont Methods Tests") + class PFontMethodsTests { + + @Test + @DisplayName("Text ascent and descent") + public void testTextAscentDescent() { + assertVisualMatch("typography/pfont/ascent-descent", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.textSize(32); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + float baseline = 50; + p.text("Typography", 10, baseline); + + // Show baseline + p.stroke(0, 255, 0); + p.line(0, baseline, p.width, baseline); + + // Show ascent + p.stroke(255, 0, 0); + float ascent = p.textAscent(); + p.line(0, baseline - ascent, p.width, baseline - ascent); + + // Show descent + p.stroke(0, 0, 255); + float descent = p.textDescent(); + p.line(0, baseline + descent, p.width, baseline + descent); + } + }, new TestConfig(200, 100)); + } + + @Test + @DisplayName("Character availability check") + public void testCharacterAvailability() { + assertVisualMatch("typography/pfont/char-availability", new ProcessingSketch() { + PFont font; + + @Override + public void setup(PApplet p) { + font = p.createFont("SansSerif", 24); + p.textFont(font); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + String testChars = "ABCabc123!@#"; + float x = 10; + float y = 30; + + for (int i = 0; i < testChars.length(); i++) { + char c = testChars.charAt(i); + + if (font.getGlyph(c) != null) { + p.fill(0); + } else { + p.fill(255, 0, 0); + } + + p.text(c, x, y); + x += p.textWidth(c) + 2; + } + } + }, new TestConfig(200, 80)); + } + } + + @Nested + @Tag("complex") + @DisplayName("Complex Text Rendering") + class ComplexTextRenderingTests { + + @Test + @DisplayName("Text with rotation") + public void testRotatedText() { + assertVisualMatch("typography/complex/rotated-text", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.textSize(24); + p.textAlign(PApplet.CENTER, PApplet.CENTER); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + p.pushMatrix(); + p.translate(p.width / 2, p.height / 2); + + for (int i = 0; i < 12; i++) { + p.pushMatrix(); + p.rotate(PApplet.TWO_PI * i / 12); + p.translate(0, -40); + p.fill(0); + p.text(i, 0, 0); + p.popMatrix(); + } + + p.popMatrix(); + } + }, new TestConfig(150, 150)); + } + + @Test + @DisplayName("Text with transparency") + public void testTransparentText() { + assertVisualMatch("typography/complex/transparent-text", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.textSize(48); + p.textAlign(PApplet.CENTER, PApplet.CENTER); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + for (int i = 0; i < 5; i++) { + int alpha = 255 - (i * 50); + p.fill(0, 0, 255, alpha); + p.text("Layer " + i, p.width / 2 + i * 5, p.height / 2 + i * 5); + } + } + }, new TestConfig(200, 150)); + } + + @Test + @DisplayName("Text with different colors") + public void testColoredText() { + assertVisualMatch("typography/complex/colored-text", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.textSize(20); + p.textAlign(PApplet.LEFT, PApplet.TOP); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + p.fill(255, 0, 0); + p.text("Red Text", 10, 10); + + p.fill(0, 255, 0); + p.text("Green Text", 10, 35); + + p.fill(0, 0, 255); + p.text("Blue Text", 10, 60); + + p.fill(255, 0, 255); + p.text("Magenta Text", 10, 85); + } + }, new TestConfig(150, 120)); + } + } } \ No newline at end of file From 6235cc9e4388d00b84e6cb77859c9a1c157e822b Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Fri, 24 Oct 2025 22:11:32 +0530 Subject: [PATCH 32/37] added screenshots for the tests --- .../align/multi-line-center-top-linux.png | Bin 0 -> 1275 bytes .../align/multi-line-left-top-linux.png | Bin 0 -> 1233 bytes .../align/single-word-center-top-linux.png | Bin 0 -> 4944 bytes .../align/single-word-left-top-linux.png | Bin 0 -> 4394 bytes .../typography/complex/colored-text-linux.png | Bin 0 -> 3866 bytes .../typography/complex/rotated-text-linux.png | Bin 0 -> 4263 bytes .../complex/transparent-text-linux.png | Bin 0 -> 7043 bytes .../typography/font/default-font-linux.png | Bin 0 -> 761 bytes .../typography/font/monospace-font-linux.png | Bin 0 -> 775 bytes .../typography/font/system-font-linux.png | Bin 0 -> 1401 bytes .../leading/different-values-linux.png | Bin 0 -> 5905 bytes .../typography/pfont/ascent-descent-linux.png | Bin 0 -> 350 bytes .../typography/pfont/char-availability-linux.png | Bin 0 -> 3530 bytes .../typography/size/sizes-comparison-linux.png | Bin 0 -> 8814 bytes .../typography/width/string-width-linux.png | Bin 0 -> 1664 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-center-top-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-left-top-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-center-top-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-left-top-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/complex/colored-text-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/complex/rotated-text-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/complex/transparent-text-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/font/default-font-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/font/monospace-font-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/font/system-font-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/leading/different-values-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/pfont/ascent-descent-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/pfont/char-availability-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/size/sizes-comparison-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/width/string-width-linux.png diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-top-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-top-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e8455adf5f5151009cc442822b5a0262d5eed6b0 GIT binary patch literal 1275 zcmeAS@N?(olHy`uVBq!ia0vp^(||aIg9%8!wv1cOz`%0I)5S5QBJS;6OONbmk+$_6 z5)~^rl)~6t1YIVGgiUOa5Io|f9cb;isH1o4f~l=dy(}_ZTZLTKu5sbCQ05PO(_|r1 z^|+<5qtHe8{;`Wo=<`suHeEzKT2(n&fUF$-6`j8~J9;cGWL5+ShGY4_cC441E7-MVq(!~YdUMM*|8 z7ya4&`}gm;bLVDfXWwGwS#;}1D2JB!vJD#wDk~%B&73i#qP~85`k|!!{PPJ0De39A zbIjgKv1Np*h(@iw_WXIep@g*b?mc^CBqd+IeS7!r-LLr{Dr_!az6@jr2L~tp`F+Cw z)~#D%VPU6Eor+N0pfl5<_2kKu;o;#)6DCaxii@)|F}ZU8{`_gv)bjfG@t!?p!JnsE>k5JsEGqXT+*WGt#&YWp&WxfCB>({T>3-2ei-q@7V;#8RY=lSHGo}T^y zE>>@E@5;)`M^QQ&8Y@<;_+fR~9H@ECi;q=%#l^+T%gb-I{%6hz<6Ld9t|9FxS17~T zF40vCvqZH+7)`XpRxo9RtqEYguqI+5*MdmFfE(AZmlqT~`2XT#g_gE`D zZ@zr*-n|<)GPXw5<{!T3uvuw=!3Wl86W#llF9$atG_bSF+kEp(TCux(dl@?eC=T3i ze)_t3-#$IRb#qyP7A`q7V@r*_-GAm)t5$8<%&;KxVwi;Q>FX~`UVZ)b_HFK4CZMJ# zqHfndi3S0EQ+rAjsAtci18Ko0rmesI)=aARQRAxE2F9~mjG}D$KkaO6X80_-crmb2 zh}T40iYxiXCs{c;KQFIObI%@s_H5Z6Mxf7_xRjft_bqIkHZ{Rvb5g4o4!^UA9NQCb zVQ+u`(xpomE*KaXeE9KWMJyZ8{)R&bwi{pk!u__a+Sm6iFcx0D%F4~n4Gx|R_2aQs z@jJF|U3&fXR*;V-hDc1VdwBYE_t7N5@({3(wg7#kZ)0O~=Iq(4FH6!kTh2;oHIhBC0n!iQ)?v{0c+2aJ?=2?XM%AR`F-b8OkCYe8t1MRMpM2(w9TIYK@7}(>d-EpeT>%c3Wy_ZB+Pzy{UH#_Q z`u;x`Zr$3oXHQI2R98#jCHusrq(@Jms_ypi^3u}QmX?uu^ZK>7r)Q#k|0P~uU*D}; zw+agjhlYmg>gs+v-WIVoOr=NYc*`55DGS%F>)We&^XS8eiEeJkPM_x9y>H*Xw3+YU zy-Vr;CobgHpMU7`<>1VV4-0sBcs6w3ycs$9WXOk3Ri3@pPbU~u)d+iuzu(lt&CT7~ z+WP7Df1nQk5~uz5=g*sGXK$~+Ha<4C*6tm@hlH)H&?f$4`|Xx2UAouZ>x7P;-m@o9 zHhfR2tE;Q5{Q3OzUq;Tw%S{*Vnl8%2cK-bN{QUf>(uH29*C_p(*J96mJyLNYM@)Ka zi&DW{QK7B_rC}Z#Ovr*$ueHR+#{T~Od*M+Bg@C}o!s24(-G*kjZ{N<%&E@6g^;;hN z%hZNX!Yc6xe@;|ijlZ9tj@aoNH)foEy6N^?AUI{6p)@Vv@x=TSw{Ar_IyM$fRZHS5 zwft23|A%-$WaP@2rJY$$y6*ep)?d#t%g@c#G|y0)6L5E;es@$*kdkUYe~WONyqT-b zr?0>NhOM4jyHLwqqkDBry!_95_wV1%**0;a;Lju(gDNN4{+jl-HX$LQCFzrMa&k`O zE1bH(d3CRj%+FUZs)hcU5b))$`Qqo!o;5W$zkdDt_wV2BZEQ5QXBw3p`VgPKWsdl+ zyYF7TdiC<<%c)bR&YCsr*fF=KvL=GwE3Q{QeD-YF&781xmt1rf>OF4-1~FfIbL}E6 z^NdBa0`68mJb2KttZduK6kupX?F;5@EpQTj-g+?M!^e*wU%XhceEISfD_(s48oJM0 z1j!WKqE@o|?tVof=l@$JI_5nT`c-4cf8y(KRX6A9TK dSVa70U-CyP?6*=xF|ep&@O1TaS?83{1OUN5L(c#J literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-center-top-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-center-top-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..8db3f7a548c93b1ea068747a3f92b106e43d4a0a GIT binary patch literal 4944 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 NXJ%r7C^mAw`yY`w3f%wz literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/complex/colored-text-linux.png b/core/test/processing/visual/__screenshots__/typography/complex/colored-text-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..df8be566f8ca649715f5335bd4f8fa4df5392b81 GIT binary patch literal 3866 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/complex/rotated-text-linux.png b/core/test/processing/visual/__screenshots__/typography/complex/rotated-text-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..b90e1aaca7157dabc6fca561eaca85fb95d30d5c GIT binary patch literal 4263 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/complex/transparent-text-linux.png b/core/test/processing/visual/__screenshots__/typography/complex/transparent-text-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a9118ac7c6cd2186d0b29ec1a959e57262eff819 GIT binary patch literal 7043 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/font/monospace-font-linux.png b/core/test/processing/visual/__screenshots__/typography/font/monospace-font-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..2964d7d80aa8c74ee3e9963b65c7256ae5296f78 GIT binary patch literal 775 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0FXZU^?gN;uum9_jc-8Z>>OyW9lj# z?;dUA>b%tY!l+l_P;8fiM2kk=SN?+JfZ0k~O3kaoS;R91);e!4)p&O&OHel_RM7Fh zi0U+>?>mi4CHfv8c_>x#@K4y|-!{ef($7tA`k^cM;?Vbo<4KJ$P3WwmSX-lbdl?fRd7 z{#obsqQolVX}V3LCNR#5^}Qw~pM(O37^p4<7VTnRNQ8$){3vv2EN-6x;U^%$Dr76 z`R6}>Vhk@?0}UzKsgT%y(TUy9&+pPplWkJ>iY#Qd+_~nxclDVcKP=X)U%!5fLf7Wa znpxEECv6fm63+PY}+roPbNyalsoOSk^` zF6F#t-#)vT4X1Y9xaO@b#wWE#ZE|K|p`qeJ9z%0;_Nz%7mjtc+`}c2+T|d`4!y6s$ z>#zU*`_D%$SVQF7w{JFb{BfMEPDdOTRAnALWd5+PZR_^!;S+Z3+ZQG^>Ex3G2O4xZ zFS*)VT1M(k?^G1vcpxCa{7h4Z&)(i%`&dQ$JJ;7e2^N18#k!ArE&cT2gTv`X9dCj}UF&fw|l=d#Wzp$Py8oqomu literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/font/system-font-linux.png b/core/test/processing/visual/__screenshots__/typography/font/system-font-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..82e2a82708551f1e5bddd497e93ecde5c250af9c GIT binary patch literal 1401 zcmc&!`BTyf6#t5t_fezfrV^B*30PjKiJOvHv}LSXMy^+)PNGguyH2&lkY|_c6*##9 zDr=L8u6T=LVkMNB5{jD&h_2<4#b_#vl>6Og|AYNue|YbGet7TAJ3jB-B7_|?H`!$Z z0Dw9Ect|*CN&kou1k_tJh&})q1mQ!1Bd^3Rm%l+#$=0H{RlDb0Gv{Z8Pi<@SSk$6P?BTT1alWp2@65!adIb&xnERva9UX$&@YDY==^?*cMZ{)^(v_ zEkn7H_%Y>hQ7GJ+qcVN^JA!C*+!j)rMV#6*2t)+@$vD~r%nladele6KwNkB%Rc=6)>va(gJ){RN>_VKxT?b?X~y`4MDg~H&>Z8p>O z5(Jq-(fMLRLqpMV6biN;P(QYDQKe{D2jY{H8Fadjw>Q)>?8+6t&B(~e(ap{7Kl^cb zyz%JJ&=AVaZGB^7zq>oXyW7#h;oSC~7LPf-$e;rBb`%Q5Sd)rOGAd~_V7@=y=iou4 zOttUM-MggSXbi??sl2@W!Gi~cmqSA~cY-co_Ok!5yiAuF#ORF%A8tM$9ZmH0@F*b< zy?oiksIRZr9OyqkaQn53MQ3ksehri9h^D;eSD|Q!=8xH8lem*g<#5~dLJNRLi=y2|ZganCHy1J@) zCpIJ)28s`1u_Vm7i7nmTkrBc{;0|9?mzxVc3&r7ZTCG;0P&h@+l^9p;x>nrA;~}W& zSAQG)Yk4_6BqXRV!nKNrT9lC4%u^37)4-{9h*(n$@c}bbHu(()CwNlUv3kzW| z*!x+t-MiVfwayE6N07AC`C!@Gw@9+TzkepixsM^Jt`?4um&b%~nh+j1=ZIHcxpO%%?t|7<9!psWt^6;Tw^nRHc$v!L^o6~=o6Hb&! zBxrJSGEylmEiLWeru+Ok!3!Z%W#&X6Dhm@ha9dMT(>LmsmF9EMAZS`dHOP4P22&Hr zLs~eI$z-BY#g&ymL`O%*#(wMV?PLLVaH{~;H$1!_i^aOS8pWooQCvST$BbH`fJNsx zI64l#c!6|rQ33bZ>|vIVUzu`h>gE$OCP>9PH1I03>BH??H)?A3EC6Xja%yUiKv3(2 zL?YFxlL`eR3&!y%s?~2h-CsV!WxS(dM{|)L4 Y+y_c_W$#Jp0ADJAKN=R&h>Oqu8@bJkL;wH) literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/leading/different-values-linux.png b/core/test/processing/visual/__screenshots__/typography/leading/different-values-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e7260fcc7a5813c62ff285ff5d230cc5c2ad646c GIT binary patch literal 5905 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/pfont/ascent-descent-linux.png b/core/test/processing/visual/__screenshots__/typography/pfont/ascent-descent-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..85dab54b370603c1bd2d11293a609713ad6d0938 GIT binary patch literal 350 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/size/sizes-comparison-linux.png b/core/test/processing/visual/__screenshots__/typography/size/sizes-comparison-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..177befc9312d91f7376b8f1bc983313f97f01730 GIT binary patch literal 8814 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 Date: Sun, 2 Nov 2025 00:45:29 +0530 Subject: [PATCH 33/37] added typography screenshots --- .../align/multi-line-center-bottom-linux.png | Bin 0 -> 1261 bytes .../align/multi-line-center-center-linux.png | Bin 0 -> 1260 bytes .../align/multi-line-left-bottom-linux.png | Bin 0 -> 1214 bytes .../align/multi-line-left-center-linux.png | Bin 0 -> 1215 bytes .../align/multi-line-right-bottom-linux.png | Bin 0 -> 1217 bytes .../align/multi-line-right-center-linux.png | Bin 0 -> 1215 bytes .../align/multi-line-right-top-linux.png | Bin 0 -> 1233 bytes .../align/single-word-center-bottom-linux.png | Bin 0 -> 4944 bytes .../align/single-word-center-center-linux.png | Bin 0 -> 4957 bytes .../align/single-word-left-bottom-linux.png | Bin 0 -> 4394 bytes .../align/single-word-left-center-linux.png | Bin 0 -> 4399 bytes .../align/single-word-right-bottom-linux.png | Bin 0 -> 2858 bytes .../align/single-word-right-center-linux.png | Bin 0 -> 2837 bytes .../align/single-word-right-top-linux.png | Bin 0 -> 2858 bytes 14 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-center-bottom-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-center-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-left-bottom-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-left-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-right-bottom-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-right-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/multi-line-right-top-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-center-bottom-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-center-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-left-bottom-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-left-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-right-bottom-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-right-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/typography/align/single-word-right-top-linux.png diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-bottom-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-bottom-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d7d33e10a1b0a3362787e6cb969e258a8bab32d5 GIT binary patch literal 1261 zcmb7EdobGv9RGQaU*Ryi<04w zxrpgV_uOPvJx6j`Rv)!`Az?z6w8O~@=@rb}6o2ni^q{xy(S?=sbuXUNr^*ev9Ezdo zJNb(y-R^<#{?3)T+4Te3T8>ypVtjIO%$V_YdiyXqG5VV3Jb;qS9el~OuK(&n#HH#q z-ZQNZ?bo+JC0A1%B&b;JJlZ4TI#b}#CGxOnhI=QP`FUg8&9kw>cq@%Cg$HtRG88iA!xc{ zM6cI(cl(!7a&mq%nbJ7S8k4EKMcvraGSJaM5@u9TA~C$lRXT&=dY8#$3Iqa~Om=j1 zG&nf8WKP3mWo6x_(k7Pq#B%LA$xs9BXmc@y_OJZl|h7aJR^kFOb?n23ps z`)OlC;I3O-1l9KT_A|4`BcSY;23yIi!^4}8i-OprqoX9LRGKbJsZltBciR%G;G|wp zX96!|pXK6%L_T7%#y5_gvDFKW=q|84gjig>H#5@^a;LCxr+gt_irVbSyocHv-k)>B z;c$=ZT|jJdG7JRAaOV(+r(*GuT>E#E{)~goO#)$2r#lHdenTT{z-FM#{*!=gx}u<@ zq!$A1$rI)3t#t`zI<)gN*LU|N`l@N9v-47mIv_C6OFnny)O~}2x&i_kVKkzyljxpV zSeP(Be;EdG8&k>Og?*qnvdGEJ{k#+4;-<0gHx!zctrXGEZ1&q^Gr-S#8zgVIf#0sK zuJ+%uf>ua;yAm8_#9w!eo5cgA+!HWgF3yuT^2&r#=!iI6U`UA1jc-1sM27R2rjhCC zX%@@HL=8$!P2D!P)Cg_P*4Y`{qOAM>hX^A>dF$tON~LmdZ*OgFjmP7uRM*IFK{uI$ z;Y6uYyFKeClFJ!XDwRxbh05LCvwh}JxdBw?7^(UBsUqykIUpPB0dJn!E-x#i1+n$c zvnc>1&YqEHc)3cYG8&DpwaVp!u^qIjkc+ENb`PI?-@)c;a4?(0f%Y~wHp&MFCz7Dk z$GzXo3yA#KXzl2u)oR0U|H-#dBW&SS`No>O+V|QYD4VCI&{jHc2*vnp(qsvP5rRZY zghF&833WUAb@VXMwoS9~ qM}JdxbRRK}>|}|Je&;ofa|bY7chdYwn>ho$F(4)~E<%h-|LiX*Q(Q&> literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..91f425290369035c574ff360d667f02d4d87c524 GIT binary patch literal 1260 zcmeAS@N?(olHy`uVBq!ia0vp^(||aIg9%8!wv1cOz`(NE)5S5QBJSG^W{iFfB-JDsmM zT_I_F@6eCAXOibVo)>R?{>+{GpJ!+9vRnOlPVxQNTKjW%&erNaJI?MrNB$RQo79>K zHl(7B+FvSc_Rq)==zQsH(sV<#Yu3NSMF*c2fp`_7S+kxcDJced#k4NiG>Io8Y)t^` zg*6cixfVojbP{#goYbn-kam+9|P`u+R+ckkHDJ0@FMTi?EX+1S`v`B?qBKT9@mF3!ze zyJ%69DAValoS`#j&8jLbRb|V}%nS_;RaRDRZf=&8lw@aTZ{*F*&F$^&y?ps{X=$mf ztn8<)j%&iSwOOXA3cD?oU@wcZ3D6_n;s4u2lYpkL z|M2FGjzQ-3+dwZKO-M{Um|`SauPDOiznn+=2+)|g#Douunm`BZX=+aV{$G8KK=d@9 zEnIu!^2^G;UAh#M@DCUcuXp8daLS1^a$5K!;m=Rcu&`_HDy>aZr%r8ZVk#8t?&?~# zdiDNyUwFehbhYdL)crqt^yuBYbL$@dXP6awNy|qAN6>4BFq&wGtzgOsV-bF&E}n=7lUqxUlaUQVqKqxad(U%zQnQB6(E zJQwD(U2RUx(fbxJUaYC9*|?ka#+^G;%oTuEIwa;4oHjSQKwMI5+g>9sE`Iy=?b)+u zx3{&)$;o{=&4BRd`em74zkV$)E`I&`H7_sk+O=zc$pWP(hGc}CF3-+Bon!Vooe}8f zl@}&Vd;j_K=DT^~^H=<0Ww@{=L%V0~)9R9vDf8#&8_moyi?%s`Wj{N}w}xA4{?`3} z{P^+7lb-qc`I(t7U%y_xrwbes3(qd=(7ms4D7C7pD#QPN!GcKDg@-=q#s_Y*XlmSe zWAeA-g))VA*aHWr=bED)^KR_^cv{KfBax=Z(qLq z?k`q}3u`8YB|0%ea~C3t5!vwQ>VV5z;(b(veqB$Rv0we6NR^G8xG*p0_tTj^O$WG6 e9wrfe)_Eakt5%>14w?;{!#PPyO zK6@2aBWD(6dU@h7X0wVS6PC72o$9f3$rrUv);sl5?ilGM zAJaXv=l`7RdydaEFSdOCzkJ@u-)sDy&zZUGdA859NDmH%Le5vh4!lB>+FPC|Dpq=U z$PvL5uXgG`Ud(ebK{%K{kbk3k%KDe)E{C5Mt!!VxzLrzfv&hjR$LpByiI=HICD(0s zauVEej*HXvft9w3NaO1@69QOB5xg{O!{*J!_4W1@pWeP*yJ^#>XV2UUldsg+#ryl8 zfA?+?>kC%^$vexw7Wc z!vY>2o(;`6Z$?f&ney|15ZnE^ULF#b`#7IAHz++kbjT?&ap9+b|D6t<==6K>waVDY z=-KnX5my`?(Fof`{VW9U%q^C*wJ$4(xsrhJiTgtF%glFkdPXi`|3c$ z)^a9SN7n8C|L4!2Z{Nad75y@2hDO|<{9*Y+d~vcx?MiL@j;&j--n)12#*G&xR!`Rl zJ#JVi+*W73I5obas3>V`)bV4-ZruM`wtMg5#m=?tzDI}%w#4%I8E z@e&pPIajP&6*PZoXO@%hZ>R0IbIoR(o10%1&rq6Uu&q|HJ1QthNxh%HMYt_b>(D#y zy>aW$rX|a-c*Q;CXy~E!-&^hGuV1q!Cnd$D(uhyOc46P|Lkkx!JaWY4=gpMYuV0^% z?l3AjbR$meTdUQtc`f$5*CQ1da>S&!wkQ?M6&30_P#Wf;!GtV0^;*lW^)=6*J&THp zl9H18_51hx_wTPx&jcECLI1{^c-eLgcmfVeykD-Z6;`@NIg Vn;gx516VFGc)I$ztaD0e0suZsOM(CZ literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..cfc6d88916113655d63ea0c9dc538a019fcf5596 GIT binary patch literal 1215 zcmeAS@N?(olHy`uVBq!ia0vp^(||aIg9%8!wv1cOz`)Yx>Eakt5%>14w?;{!#PPyO zK6@2aBWD(6dU@h7X0wVS6PC72otkkJ3sVD*T)$ot-{S zm|#N$Q~z5_-1_eviw>$SlUpR0ar3kwU2i;vgN>zQ@`{ri_M zZ{EBa7_V^p0_W#XpQcTlHs$&8NMhP5&hoc|^!+?z#4M z_A&!2tE##>I~$ujw{P#>x>a;fqwUJz;NaNU*vXS8zxi=r`~KFgTXpsIr%#)vl-Tmc zf5wz4C543t`6f-CeERfhc6RpPzkUS;2bY(ZKb4*}d-m&Buhi7kR;^wg9UVP&J@2NQ zIUW*`*!V0nwg)UFS`C8H>dJ(^M@x-cGg^a zSir->v!VIs&B)0oQ+^&0Vw>-$s?sBKzva`x*-8%&9db%cT=?nVf2TtyI{jXJ{%K@n z^z8Zbr=O=!pDw@tHw&loa%ZP+-5;;-{_^F^_5#kCM~@!8b0_ATj+lr@NJvP{pFRE) z42q(JkA2(5)OY|GTxZYr{&Jdg$!PV4ed-VW590`PL_{n#UHDG>{@Jr1yA9{5W~_RVy>IXGJ^TlvTUCxBuds)uDQ2$0y#X*x0QRIzM1v=}5>h^y6`ulHcYO1ob@~O#%2VQWN#{A^t? zZQHhGdm1&I)qCFNCO%!g_`TQ;57sSSrGLT2G%-i}{@=fU=gg4-M(6h1TWgj{aXoA?TvNl`cmOE=@L?h_=>!H| z{QPe!72SWZ8I}7KYskrwOIX1 z;Ztj4X9#>T{)SqDr3o87}Ir|N;dT)Xj8s(jt@<;&&z-D?|-bT@Oot`_=df|7|4 z5em+3Q?Ip5QjvAEX#1CKC}4S5^-I;>ypFbq|B9p}ScE3Ew>(i)tn~1ZBZB#zJ+@GC VZ|IV{M}Y+sgQu&X%Q~loCIIY^PLu!u literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-bottom-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-bottom-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..f55c6d27cfcfc04698e0c8f79b29e514dc9956b2 GIT binary patch literal 1217 zcmeAS@N?(olHy`uVBq!ia0vp^(||aIg9%8!wv1cOz`)Ys>Eakt5%+fPZtv_!k@oc+ z5)vUSg4)cBIFvlNR(B}4v248Hb;WOj#-sQJ3K1GNJPx=o3~O33b*e_w`80u}dOuvA z91}n0?o-2JI&-gWa^AXhiTuQTeOj!KX#F(`Rrz z@_kWa6{{e0DCm=#N>${4LyuU+g{pp#W{`}XkDpyxlAuXr* z20$a9J#*8!bm`KnRjb_G+}POJjg5@>`1!XA-@0{6N?LmP^5w5zy|S{hI<%fQack7X z06{j+qfVz4GP1ISW(Vo``S}$U75%B(A94Tg-Oe-p{r!pI|9OR09kzIQ^k{3@g}m+j z{QTWV4;^YcnWFX2NrmU_xq=A+vd1{Lx*F&_{Pd~F)6?_E&;J}ktCVlG#H}~Cwyw_1 z^t7(4uKrzmH(z0)m5bBIV?U0oM@B{-^x$M&y?Qk(D{GRzn5bxIczFHYm*x`;ZgR3P zg@=a&?YTeyg5xVL&QJ$@gevEJ@ptdum6eum-Ll0(hHw9Uf1q;JU-{{eW{S_7KR;S$ z+R2liAM5v?O?&DebiT8>yOtP2t>jC=95YHir+9}ZXLChc-^ z^4!AN`@Gd|{`yUuifU?XN?NV}qf*FPUq)8e*VFUjyJd>^DqhAYCca6C`(fV%lw8$- zkMI(*`xF}!larsHpO+^mB~?{br37;RU-6ueJvDRv(rat~-nnz<*fBR}XXor}ZIDA> z2&-j1*}ZpfuGwsu*Oc#HzaGB(uHL;su*($^bCmBte*E~)pPI=hQ#Ri`lCA~yVd4{M z4i;dDoj-T3s=C_S+uPsYALv}LXK#oUK9P3X;^Xgs|IVEy#jP!qGK8i2Yg#2YvTogK zp>Av({3g%}n6^FNoG7oWv(wjKzh;e(O>{(r#x@Kg!%6>8Cf3}{}FFgLZ!@)%5uKw~y6-MH7IHWxIEdFni(8pW_PcdN8 O#Ng@b=d#Wzp$Pz`Mi)H* literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..8eefc1b37997227d95748f6d9f14eff275718d5a GIT binary patch literal 1215 zcmeAS@N?(olHy`uVBq!ia0vp^(||aIg9%8!wv1cOz`)Yx>Eakt5%>1)S?w*=GRHqg zPg*G)G-;~l#7UQQ*0_1Od5NuQnR{u%AJ!?G7UY;bO>CSh6c)Bfvw!yF6M3Izl=Ls} zX*YbFc<9W}zcI(}6z}$%Zp?o_{Jx<68 zZEA0MqeujEqxzSsz4nSyNgLNIt2w>lJbL|$y366WW%aU#f{%P(6z#n8L3U%%CpDE{ zoBkzxY@NU;E>qKOMn*;!7BePJEG#HkV7qYb+O-=u7FJd5TDNZ9;>F6JwkC$H7UdK*^w7}T!s8Si zJb8!a(W4(f7W(=5DF_H$-@bjj(&xj6osX>k-`4Uy((9x^%$;hdbS_uC7oJn6aoa^~in zyLRvP1V*Fhe1mh!_cxq9>-+K95@4(>@t-k4`Tm9D_~VST z&)&Yxy|i{hfRUfX?TPCK6K$TgS#H|ow)j-P%yG(O(m+a_ZF z_tq_=*=G@+n?7B9_g%fZMUy~5<9O&sUrm2s->1)?ebpwPO%vW9408NQi$osxeZUxa z`SRtLFI#|#V9S;-PoA{oX{$`>5q`x#d!OwmUSZwKmoJ-}nZ4Ks^KFu3A$QjGN0u{Z z&U_@d^fnjg(j#&|&z(R2{OQxFQ>S*G7ZVffik?=YtA-@C N!PC{xWt~$(698U2Qds~1 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-top-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-top-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..d600d172b01ed59fdc4023c38dc8c528c2173349 GIT binary patch literal 1233 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 GC;tbI9yBTd literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-left-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-left-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..c0508b64d753e46f9dc4171a5125ad05e0a3d312 GIT binary patch literal 4399 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 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-right-bottom-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-bottom-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..846d8de0506918bd85ab77b94316bf57eb85ed54 GIT binary patch literal 2858 zcmeHJYgAI{8aD01lx3J{YC_5h&6*g~yoAXrO=>7imrY}7Xvt_pii#m7!c?TDQ;tq) z7s*jGkExiMmq4M>%1d6y`vp)GF;P)dK|z^KbJl78p1)_!UTgo@@7nu$p8dR+@4bB* zi{8A^WTTdr*5(t(-MqB4*6h>VAZ;MB2sO6Q(%LwC!tIDR<-!y{B)Cfqt(z5Q6zr-C zD~!xG-eYwiroU7FK*}z!e2@XrochhUVdL-pI)uuU(y-t7&JVX|9j*X&8CZnFITVJ;b5G^-*xjh~&T*(yKJ}NO(`o|P4(JvzGuML#^IaP-Mzs^NnyHE#Y3SE z1pFT^x}OD?I-;J#ab{;IaNXXS4$6$9uglR-jCnUz0!98>3uxm z8|p2?fS{nf*9y_Qq^Z;9<~1|h&oQd2t9S3-z50$oApAGha@P!*n3xz>>>M>ch*GOM z0CEdm+#2`s3@6gFva*HJRu@dJ*JA9tI8g1`2Vks)+YT2C-JRgLWRMP;F$}AD0}fDR zcNLsP;J8$AA^jzn&_O|xJEjhkon}`nU@CDs?wYfXjt;qIL=SkYkE_?9tohkV4wVrouY-~)75=URb=aCLLI#T9F8~ixr>iIt~w|kB-hHae& zy*xeJ+uCO2Li#`-ax@g}cYA4BWkJRDAhWgNOX z@tP!Q{P3Z=qrzD=Q;SL47rodQbhhyO;iIRNo)Ki3Afy^J3GPOOp~VQljxv19NL5-I)LOm`BEMgBo1EbRv;EIk5+E-#6)r%w zG3opJvNA@a8O_bjWGlhyin4iDcMZpqwi<}XixoIET$4L)FNnVU(ULB0F~U&;;v-Te zU^X%|UUX;0%C9o)_bn^wOYQ3O#A>`H5V{#!3}`bl9UdC;_wjkiTL>8_*Km*+$Pa zgiJ{x)S{-TpPC)W!Sz|L@lB(>nI{6%Xc~3ns96YI6;7A7I)D24F!NXH-4|T002??0 zO%};81VRA{Xfr$Ia0`6p5YX+b=SnFP`FWEcHJX5waGCY*QIL1SbYL{_Ph#7qG%_ZC zwEZ`WRh~ymZ3WZj8Z5~crfP}-9cNDzT!CATFKU3uWyUAtK(=~$Vhz!UBm_4YZkYhu zVaB$1xRTy&RQy=g@>1{DBzJX8nGvZX4M#Y5X00VbiVEhMf4Ri#neI<3Wo2% ze)y~+9Y^bfK0ZiDW7Y#BFB!B=_b+@529t#P&dV#c7BP|sv^T6~ZSCcyx*UZvVrJ-w zt+D*lN+2N0?Sa4J*Po@XDn|5dflk39@9BJDz*iy0pkWB#%CNAowk^AleF6Geo|~WF zpc@q#*_-wB3(~z=u5T5kz3TKaJRVOZ7BevnP_2UiBx|aei#yp5Vmb(^9Zd*7wq3k1 zZB?i-#>Zk(Dpii>BtN0fHMFACgIvVfU!7{atq;a5ULgevs-l@pCfSZ@C*4&ua}2er z$Wgz%IL!kJS8{WMp1r+2oA(rbVR??lZR@DHwaLh`g1QtD^U?e^`&+w=G`e%6Vb;j{ zX^zZ=WNmV-??EzZ)p1tCTLE2@o0}^Zi^tT9)0_hE*t5dIrVH1OXS4J-YfO9(&gu{d zLZ9q3`KlXmuyo*#DLp#IXG}Oh-OFXND8hKTm<|WE_UIJNKbM-jdj%j#-D@4_)P!+ literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-right-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..efda9ca97b34e75851ea73485df0e7715431959f GIT binary patch literal 2837 zcmeH}YgCfi8po}sVlp+-#5APfm^PU>YMG&uhLV}4nHpXP3GM2o)Jg@)7{x26%jB3M zX{mXcz|c$q5j-!Rb=I7-*7-c2X07-Au=jfQ`|SPu|DR{S zDIV@vjZONS)YQ~8T#jN+s;R9pRlT6Kz|10SJ6ug|(})Yk;ncODnbC8!*E6t=xv@cY zw=-Pb)Qc%_7sMW%V``a^RuPD}$M(H0%NfV?IEm4@v+*kz!~0v;x|cq8NE-9c@4O^! zSI%=17-Kf`sMeJ$6%&fjw(J~P5Sk&&tIM_|^gv1LcW&cbt4UYjj)&7hYkq`(*GAj2 z1@YJ5&tzb_w8TY^Byt#sh>)~MOfVvReC+Y4vTnG z;B*SVES-a)$<^92DyY?%_O!aXdY)Sm*bth`^7irJJ$AV(UQezw<-i^S862p-l|c)L zD6;_ePsuES8PUu>^8I1dy%RL^1eq@0#U-Ww8q=O6@~~GW@uzZ`=*qx`TFTsq4s2F`)d}0Q(8jYv+2)eSY!$;Whk}oWlg}mAJ2z9)R3tEByzq1wG zMqoAdtM-dWB4h)wffxKt(CVxtznm?MJ{)D zr$LZp#ZnDJ;iinHzRt0hAe%1?n4!k!l6}klyk)VH-m%{ZSZdepR6vcBkxY4DjWC zHD5*>2G~&q@i%LLk{Eb^V{gzh6gj%dJTC(qK2vE(c#*h%F;eIEGF%m9MVT_-KpmHV zYIuYUZjPoL87T4U7<(2UnQKoPf|VBKX7tW+>_z=0Z+jRhm|fhN%*N~Up{+?8laQj^ zBDWl}boRyOmGc0j$%pc`h6LH=t2j$sVayi1^yD(_LtBb=jF7hg5$EkBN(}?I*6+X@ zNvyLv5}p2@>#JC^;ErMQldqLL21&li z&%Y=jQ@xkl)AssSu(__v7f};0`T0yYru`_h62NipTnTi^>Dg*>?6k6AW~9D~vrtd~ zN8KymLGL9oWJ*Om3PR}q^+zS2SMERjJzf5ahw*POzWs>@P;iCjSCJKQ6d@q?wS~{W zKwllp#Ii37j-hlS#P6CWuRXXis*R?wh+X%+DT-UK_*w zhKX2{ca^Y0zzc)XTUPpVjA47dPqRHOP$m5V$727wa3#;!J@>3J>rNLqQ-A*} z_s4>vSvm@W45~h|Efb0g-Az$sW8z9ws-`KmD}ka@@{%>7=TC$Ii2~S>`HX+Ip$*%| zvN8;;mk1jAR!?DHj-OOj#Z=|~TWi*B7<`NC6o3ZG{lknc=IGct32{-x4`n?d|J&y| z&K$Br^r`IQW$JKUM4fiFwBwOfZV~hOlqJp$t-@GU>NXKUGgm|&Q_h(vo715v-f~MWZx1;UCJ?9hE}!OvzJ9Pck<&|8aqJ^q{iY>DA#!c6v(UU!UnJ@_6uub- z-mT}}xw_r$mZ4GtU~rZ;A80~geB$`M&H%Q%lg>7+M5&O^$XN^4*wa8iein1#%40%Drje0Rtko|%s$i$O@X@cqKHUll z19dfXhYjl@-$IfVEpQE1zJbJj?kjXz zRlwnlBh)B>?(gtucc%R%cs{Ga1qHcW)8hf4a`ASQG-*%+p>Qf&tOMyLLKLZ|H&l8vMx& b6zU-#+mpK!g)S>I)=+Hoh} zz+x}7ivf0H&?{_^dbLOXk@-ZuQWhUe5l_ZGSGT`u<0IN`_OW|C+t}hMqJ3;~xc0r| zNmDHq6>PrCo%YKPu=WKvP}2Wj{$?I(}Q_Z3ZWlYGrSOxR6QXEswbOD)GJNS=YKf4D{udR^as>TZ243 z+i!5$%4sC>evyy$880sz|KXP(>L|bH2PxKqDaxs8%C~L3SZY{ev}iwWKl^h30i@6I zr0>bM41+>K3f?G$?^C8uo153oZ1QT=WS7~MYM4rtiM{5mqoYHt8`T58>Srs*TcH%i zNWC7axTNF?H23l2$7hOue52w zkhhl?x1(c5E}#zfBgewgju$`Zs)gB<TJtN=8+;ijHTD8dLvGy{BKULK{#^2;sZ`yycEc`llF&VzpF zjGx~`K%l?7HpEiCu7vf@@tKAQG4L{|es5W=5aQb1-90Vc%Urbj`O>BGL^wPGmUZaL z#2bRR`Qt}cXSK6xrXG{A@9JWI$l2nbMvk6RdPNgu{ID`eUTJ9?DC_c z^$n1kXi>5;1TSF6kN>WG$Bb7JV|{I%O0R4UQv9V#5;ms6<6 z!*ACBwW~^v%;J!^Jl>QGVrfHfvG2u1_rX`V6ckmxBA3t4_vP3eD7gu$&vZE|H=4S! zZj-8{x2vl!$6s`nHZ5(Tt}ZpSy(f*_GE!AkgtS*}gUBD0+$Lsu!fgM2OAO>?rrHI_ zHYRg_e@@oeRa$FnE76L-x}t2I)m_81q^t(xa3Td(4cFw3-3OvBf3l=X+l;W}pv35O zF_?i2PZZu+u?nb({Bz4n<`P$ZE`o)#1VT5%%K&XgrX$0{fxfTgE!BB0 ziQX6*82A=7opm^_XUpcO>>77UH>r=Chn_lJ+ov((0xSj+H)%taN_)$yw>dS5LD_NB zPRO)0d_8KK{JGVE7}}WQn%FYdmwh5QgQ8IQ_OUC@h%#q|pSVgiUY!fP%aiWCEjschaL{N+V

Fr?3T$3fy!cI}~htNzN!si>Fl7!zN(@ZUNpHZ5JgTbVre)RTEuSbj)0PPK{TU&o=sUc6HjGh@j zVrwkFxDpJAa(fu)_}v$&tBO%QTcA@skoR@IGT>?v$G!snD$g%0 zY|@R5iRsIE_7&;=EZe`9#H~Gj42Q!-M3m7nG*G<*A0%t3o=Z460HQkx$ek^S0ES(n zAY)aaF~-+oQYuwS@=~9Y=bGA4>LE7b>~BtWKGp~07B3Tm`L$Q+bUM+FZYSMUH**ZN zs>oBnx-iWF3Riq{gPy&;J%jTMeSUeal-fn5130IX z&kujP)8v~Tz`=^aJFc`S)hJhC5yN0GTA*|g*ifIzdu{lgUl)%TmO7pcN?ef3dwY6- zQPu5lUgV__+`lf>%G&w`@DvpxMIMrY0ss6&DC*U*;YA96~a)I2gfM z9Kc7^&L+g!**Q2cHwb4(8wd)v6I=-V7&d<4j8Ic;uT)9eOLsx_XxepH-3&NrSY!VV zM@`n&{}*9w3_fM3j@8|@HAs?sZts}k@*R+?{cBWsj^M6YManTplE>d{h4|mDg=*~` Y)$b=B-1EBu?7&(lj(ND19u2(lPb9(kr~m)} literal 0 HcmV?d00001 From a8ff501aa468ec4c18cde3929d3768c8bcc39346 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Sun, 2 Nov 2025 01:04:05 +0530 Subject: [PATCH 34/37] shape-modes screenshots --- .../shape-modes/arc-center-linux.png | Bin 0 -> 639 bytes .../shape-modes/arc-corner-linux.png | Bin 0 -> 639 bytes .../shape-modes/arc-corners-linux.png | Bin 0 -> 639 bytes .../arc-negative-dimensions-linux.png | Bin 0 -> 446 bytes .../shape-modes/arc-radius-linux.png | Bin 0 -> 639 bytes .../shape-modes/ellipse-center-linux.png | Bin 0 -> 958 bytes .../shape-modes/ellipse-corner-linux.png | Bin 0 -> 958 bytes .../shape-modes/ellipse-corners-linux.png | Bin 0 -> 958 bytes .../ellipse-negative-dimensions-linux.png | Bin 0 -> 1010 bytes .../shape-modes/ellipse-radius-linux.png | Bin 0 -> 958 bytes .../shape-modes/rect-center-linux.png | Bin 0 -> 704 bytes .../shape-modes/rect-corner-linux.png | Bin 0 -> 704 bytes .../shape-modes/rect-corners-linux.png | Bin 0 -> 704 bytes .../rect-negative-dimensions-linux.png | Bin 0 -> 177 bytes .../shape-modes/rect-radius-linux.png | Bin 0 -> 704 bytes 15 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/arc-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/arc-corner-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/arc-corners-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/arc-negative-dimensions-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/arc-radius-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/ellipse-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/ellipse-corner-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/ellipse-corners-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/ellipse-negative-dimensions-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/ellipse-radius-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/rect-center-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/rect-corner-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/rect-corners-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/rect-negative-dimensions-linux.png create mode 100644 core/test/processing/visual/__screenshots__/shape-modes/rect-radius-linux.png diff --git a/core/test/processing/visual/__screenshots__/shape-modes/arc-center-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/arc-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..124d3c8db9c9e481eb21efa1e6106111f1c3e811 GIT binary patch literal 639 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`*3@>Eakt5%+eMVV;(uL`yN7 z1rw{P`vbizbDF#hxLo>O_M3FP>ulocoW!Q$;#zl<%Zu%|YE-|Qz}fK1xAzzO?|%JN ztS&olg87>Y)~-vNZ|0at@v=3?t#42E6`pG-!W!6?Y#%s(&cu1=pC5j>;r83UrwKr6xi%-wh2egFOV zW5v#xOMmL+mz0$p3lMRUw0ZR3+4Gz?^Xsow+i%O-b-2YOxkleoeZExlyE3<`OP#-JSG&{}1^$}3aSNCC>J1bSKcX*vWxwckMLm5< zr29X)MAgM69}85$b{d8?%;>qqd8+ZD&Xgy6OfJ03_?DkBea`e{clvdvq)buUSIJ-q zbYZrPg4*Je!@pE0|<$pq=(K>4nUDCo�CA5l`pim-17hMGGM}D@O1TaS?83{ F1OTblC^P^7 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/arc-corner-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/arc-corner-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..124d3c8db9c9e481eb21efa1e6106111f1c3e811 GIT binary patch literal 639 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`*3@>Eakt5%+eMVV;(uL`yN7 z1rw{P`vbizbDF#hxLo>O_M3FP>ulocoW!Q$;#zl<%Zu%|YE-|Qz}fK1xAzzO?|%JN ztS&olg87>Y)~-vNZ|0at@v=3?t#42E6`pG-!W!6?Y#%s(&cu1=pC5j>;r83UrwKr6xi%-wh2egFOV zW5v#xOMmL+mz0$p3lMRUw0ZR3+4Gz?^Xsow+i%O-b-2YOxkleoeZExlyE3<`OP#-JSG&{}1^$}3aSNCC>J1bSKcX*vWxwckMLm5< zr29X)MAgM69}85$b{d8?%;>qqd8+ZD&Xgy6OfJ03_?DkBea`e{clvdvq)buUSIJ-q zbYZrPg4*Je!@pE0|<$pq=(K>4nUDCo�CA5l`pim-17hMGGM}D@O1TaS?83{ F1OTblC^P^7 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/arc-corners-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/arc-corners-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..124d3c8db9c9e481eb21efa1e6106111f1c3e811 GIT binary patch literal 639 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`*3@>Eakt5%+eMVV;(uL`yN7 z1rw{P`vbizbDF#hxLo>O_M3FP>ulocoW!Q$;#zl<%Zu%|YE-|Qz}fK1xAzzO?|%JN ztS&olg87>Y)~-vNZ|0at@v=3?t#42E6`pG-!W!6?Y#%s(&cu1=pC5j>;r83UrwKr6xi%-wh2egFOV zW5v#xOMmL+mz0$p3lMRUw0ZR3+4Gz?^Xsow+i%O-b-2YOxkleoeZExlyE3<`OP#-JSG&{}1^$}3aSNCC>J1bSKcX*vWxwckMLm5< zr29X)MAgM69}85$b{d8?%;>qqd8+ZD&Xgy6OfJ03_?DkBea`e{clvdvq)buUSIJ-q zbYZrPg4*Je!@pE0|<$pq=(K>4nUDCo�CA5l`pim-17hMGGM}D@O1TaS?83{ F1OTblC^P^7 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/arc-negative-dimensions-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/arc-negative-dimensions-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..a97455efbf08757a16dddd60b6797557ed8af7f1 GIT binary patch literal 446 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0FXZU~KhtaSW-5dwWH9^`Qij10Pi* zCH&eQ*_GDlolMB&2%CLSUqRvzlUn3#ol`o8H)PD?FpHlQb9<6&L2Y33$0`Sw_sV_M zJNgZFJQsJ7JI_~tY}tpYo=nvfc&D^FMoM5OKAzuHwtMdT-+SZMU(C3YWf~~5`l^=o z7rFmy-j>~dTPEt7xi#wX$BL~{M+LOlKQ!>1^nU)0mq4+! z%`;c63KTJPEPqp?H7{~m=DFv^X3rbv`W-d)p0woi&oj^d9C})G(d2EV==n8Od*>Bx z4`Oj#@cP$hpyJ6Vm&EApuikbqa{cuW(<i6L53WAxNHm}(A{TQnB`5KX&rTF!3jS9i2fkvzMLvFZE;$Mqr& Rzku<=;OXk;vd$@?2>>=U&#?di literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/arc-radius-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/arc-radius-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..124d3c8db9c9e481eb21efa1e6106111f1c3e811 GIT binary patch literal 639 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`*3@>Eakt5%+eMVV;(uL`yN7 z1rw{P`vbizbDF#hxLo>O_M3FP>ulocoW!Q$;#zl<%Zu%|YE-|Qz}fK1xAzzO?|%JN ztS&olg87>Y)~-vNZ|0at@v=3?t#42E6`pG-!W!6?Y#%s(&cu1=pC5j>;r83UrwKr6xi%-wh2egFOV zW5v#xOMmL+mz0$p3lMRUw0ZR3+4Gz?^Xsow+i%O-b-2YOxkleoeZExlyE3<`OP#-JSG&{}1^$}3aSNCC>J1bSKcX*vWxwckMLm5< zr29X)MAgM69}85$b{d8?%;>qqd8+ZD&Xgy6OfJ03_?DkBea`e{clvdvq)buUSIJ-q zbYZrPg4*Je!@pE0|<$pq=(K>4nUDCo�CA5l`pim-17hMGGM}D@O1TaS?83{ F1OTblC^P^7 literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/ellipse-center-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e0fd115f3795309df11b9ce8e8425de9226e967f GIT binary patch literal 958 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`)$<>Eakt5%+fb#w;U85x2KZ z0v;}xT<#wTQ{+#WY~;#ODY$xzlY_v<&zx(VUri{JI%w`OUu}Zg(#RWXHO2pbbAR8k z=JY(Lri+IYZ_KnVyTp0vqPZWV{;$7(|Ni{>^W(>l&z_}O$jqBNx72Fxqf!G2AGOK9 zfB$~8|7iM`Y-@5-VU%veO`SS<+uJdQoHlIFy`e)6$MLeh0T%3LO z*{@$!`|qzloVxvXY;5e>>)9LsvYKRnsj#W5th~5h?qJz&*%|16iHQ^&UF2TwQ?MB%q7CEdJ)?9qB)s5k-6mOgYm{Y~EAae0Rb$_7H z+#R;_oM-mFV41s2t>b2&9_Kk-pc&okbaP8WP4ujG#L6_KSxZ^Y@DbQ7=-b;M1N72^ z4WdA&G+&ouG|4upXcPf)UMn`F^|H;k0Sc8o`c}c0YPLwYWY#Q&H0xa|7TbYl=`jaoAD&U$nlJui78W&22lIqFtF>gSwK!@4FYl2wKL2zm8$s!66(DD z*mBCv4!;YTSxqzEzL@mjP7%kfy)SNEatn6_`W)y~pxF;y*`H5yzL4XtY|aAqfB8nR zYuAQ@jQh7w0OZd7Prz0TrvP}osQLC$#f)u>CpMpCF_gY|h2zA+Gg7>K(ctJ=D+uzo kxCe+sOiDWK>Gq#VFVdQ&MBb@0L}5ld;kCd literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/ellipse-corner-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-corner-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e0fd115f3795309df11b9ce8e8425de9226e967f GIT binary patch literal 958 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`)$<>Eakt5%+fb#w;U85x2KZ z0v;}xT<#wTQ{+#WY~;#ODY$xzlY_v<&zx(VUri{JI%w`OUu}Zg(#RWXHO2pbbAR8k z=JY(Lri+IYZ_KnVyTp0vqPZWV{;$7(|Ni{>^W(>l&z_}O$jqBNx72Fxqf!G2AGOK9 zfB$~8|7iM`Y-@5-VU%veO`SS<+uJdQoHlIFy`e)6$MLeh0T%3LO z*{@$!`|qzloVxvXY;5e>>)9LsvYKRnsj#W5th~5h?qJz&*%|16iHQ^&UF2TwQ?MB%q7CEdJ)?9qB)s5k-6mOgYm{Y~EAae0Rb$_7H z+#R;_oM-mFV41s2t>b2&9_Kk-pc&okbaP8WP4ujG#L6_KSxZ^Y@DbQ7=-b;M1N72^ z4WdA&G+&ouG|4upXcPf)UMn`F^|H;k0Sc8o`c}c0YPLwYWY#Q&H0xa|7TbYl=`jaoAD&U$nlJui78W&22lIqFtF>gSwK!@4FYl2wKL2zm8$s!66(DD z*mBCv4!;YTSxqzEzL@mjP7%kfy)SNEatn6_`W)y~pxF;y*`H5yzL4XtY|aAqfB8nR zYuAQ@jQh7w0OZd7Prz0TrvP}osQLC$#f)u>CpMpCF_gY|h2zA+Gg7>K(ctJ=D+uzo kxCe+sOiDWK>Gq#VFVdQ&MBb@0L}5ld;kCd literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/ellipse-corners-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-corners-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e0fd115f3795309df11b9ce8e8425de9226e967f GIT binary patch literal 958 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`)$<>Eakt5%+fb#w;U85x2KZ z0v;}xT<#wTQ{+#WY~;#ODY$xzlY_v<&zx(VUri{JI%w`OUu}Zg(#RWXHO2pbbAR8k z=JY(Lri+IYZ_KnVyTp0vqPZWV{;$7(|Ni{>^W(>l&z_}O$jqBNx72Fxqf!G2AGOK9 zfB$~8|7iM`Y-@5-VU%veO`SS<+uJdQoHlIFy`e)6$MLeh0T%3LO z*{@$!`|qzloVxvXY;5e>>)9LsvYKRnsj#W5th~5h?qJz&*%|16iHQ^&UF2TwQ?MB%q7CEdJ)?9qB)s5k-6mOgYm{Y~EAae0Rb$_7H z+#R;_oM-mFV41s2t>b2&9_Kk-pc&okbaP8WP4ujG#L6_KSxZ^Y@DbQ7=-b;M1N72^ z4WdA&G+&ouG|4upXcPf)UMn`F^|H;k0Sc8o`c}c0YPLwYWY#Q&H0xa|7TbYl=`jaoAD&U$nlJui78W&22lIqFtF>gSwK!@4FYl2wKL2zm8$s!66(DD z*mBCv4!;YTSxqzEzL@mjP7%kfy)SNEatn6_`W)y~pxF;y*`H5yzL4XtY|aAqfB8nR zYuAQ@jQh7w0OZd7Prz0TrvP}osQLC$#f)u>CpMpCF_gY|h2zA+Gg7>K(ctJ=D+uzo kxCe+sOiDWK>Gq#VFVdQ&MBb@0L}5ld;kCd literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/ellipse-negative-dimensions-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-negative-dimensions-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..fe97057fab6ee863c6eec5ab79ce47310063beac GIT binary patch literal 1010 zcmV9ZC?gC@10&uyD|!xQcKT=~xg7;u4fD1?eq!4nmjP z1WUn9>m<54xd^%!Czp=iQ+w7+wK2KjdXO)V5PrUI^6~L|-xuh9NP)KSF=SyGk%eVM z7M2lNSVm-F8Igr$ME@by-CgJ8C{k^;G~0^uNRf(=E{ar*f3=5m|2yA4ZK+bo*_U;&ZBY7~mOT zwz!TL6MEQ*o= z?%!DThsRSRXu^~gJT=eJ(a}1F7KKp$-%FldEDD}lW_y@gdQ7Ts>d^SNENlPHc9 zCA+t`hg~mTcGpFUCZlztIK5DZ0$UT^x#mqw$ZwJp?aHqXz`amV1rpr}_jzGoOl6vcEpz0KR* zhSgTp49o5i)PP7b75O8n+wFEHGj}JG$#S`j`Nre%^Yim;Hp5qZk1M#2e?o@RQ&bMQ?32lRXEXEJFjsKMuQU0q!r z931rfeeL_-6`t?&`E+)oOi`wy2WlPF^X68oswi%^+uUkZt5u!V`c^Lc9S)e(^F)JRUEVN;orz!{IWwaOVvMgL=JQC={a64JX7(o3WO{ z-`dyL*Jo#E;c$3=f8TDmd_y7O^ literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/ellipse-radius-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-radius-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..e0fd115f3795309df11b9ce8e8425de9226e967f GIT binary patch literal 958 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`)$<>Eakt5%+fb#w;U85x2KZ z0v;}xT<#wTQ{+#WY~;#ODY$xzlY_v<&zx(VUri{JI%w`OUu}Zg(#RWXHO2pbbAR8k z=JY(Lri+IYZ_KnVyTp0vqPZWV{;$7(|Ni{>^W(>l&z_}O$jqBNx72Fxqf!G2AGOK9 zfB$~8|7iM`Y-@5-VU%veO`SS<+uJdQoHlIFy`e)6$MLeh0T%3LO z*{@$!`|qzloVxvXY;5e>>)9LsvYKRnsj#W5th~5h?qJz&*%|16iHQ^&UF2TwQ?MB%q7CEdJ)?9qB)s5k-6mOgYm{Y~EAae0Rb$_7H z+#R;_oM-mFV41s2t>b2&9_Kk-pc&okbaP8WP4ujG#L6_KSxZ^Y@DbQ7=-b;M1N72^ z4WdA&G+&ouG|4upXcPf)UMn`F^|H;k0Sc8o`c}c0YPLwYWY#Q&H0xa|7TbYl=`jaoAD&U$nlJui78W&22lIqFtF>gSwK!@4FYl2wKL2zm8$s!66(DD z*mBCv4!;YTSxqzEzL@mjP7%kfy)SNEatn6_`W)y~pxF;y*`H5yzL4XtY|aAqfB8nR zYuAQ@jQh7w0OZd7Prz0TrvP}osQLC$#f)u>CpMpCF_gY|h2zA+Gg7>K(ctJ=D+uzo kxCe+sOiDWK>Gq#VFVdQ&MBb@0L}5ld;kCd literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/rect-center-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/rect-center-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..f8b37838e356be6fa94d44fdf11672668d0d15cb GIT binary patch literal 704 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`)e*>Eakt5%>0*r-!rHl_QU2Guz{wsC4x8Lg}Zk<`(o_ob6=J~t! z&f#QI^_Sq<{<>A~FZw@S{MWez$)EKcBB#!^n6z#(R`gx?#>XRA!&>+fcA+)Z3(xMU zy5V;AaP%*^zajP?Gxq;Kd?78)HfQm(hgRkGKG?NkF?^7lRP}W6f5zwf$MYA|a3um$ OCWEJ|pUXO@geCyJRZl4Z literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/rect-corner-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/rect-corner-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..f8b37838e356be6fa94d44fdf11672668d0d15cb GIT binary patch literal 704 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`)e*>Eakt5%>0*r-!rHl_QU2Guz{wsC4x8Lg}Zk<`(o_ob6=J~t! z&f#QI^_Sq<{<>A~FZw@S{MWez$)EKcBB#!^n6z#(R`gx?#>XRA!&>+fcA+)Z3(xMU zy5V;AaP%*^zajP?Gxq;Kd?78)HfQm(hgRkGKG?NkF?^7lRP}W6f5zwf$MYA|a3um$ OCWEJ|pUXO@geCyJRZl4Z literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/rect-corners-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/rect-corners-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..f8b37838e356be6fa94d44fdf11672668d0d15cb GIT binary patch literal 704 zcmeAS@N?(olHy`uVBq!ia0vp^Hb7j@z`)e*>Eakt5%>0*r-!rHl_QU2Guz{wsC4x8Lg}Zk<`(o_ob6=J~t! z&f#QI^_Sq<{<>A~FZw@S{MWez$)EKcBB#!^n6z#(R`gx?#>XRA!&>+fcA+)Z3(xMU zy5V;AaP%*^zajP?Gxq;Kd?78)HfQm(hgRkGKG?NkF?^7lRP}W6f5zwf$MYA|a3um$ OCWEJ|pUXO@geCyJRZl4Z literal 0 HcmV?d00001 diff --git a/core/test/processing/visual/__screenshots__/shape-modes/rect-negative-dimensions-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/rect-negative-dimensions-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..18a0a4a4676a10219588a419f764bbafddc926ab GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^Mj*_=1SBWM%0B~A6`n4RAr*0N&pUE881S%Oy!Sul zp4Umet=!gkRSxmgPj31k6Mp*G?}hH0JP^=6v3Hl6o86-G_wsk4bU z%vV@hzEFSv#{ECOnMA69;gbBYP@rh-Bv%B?I#tC`^pv4R@t{cw&@z`)e*>Eakt5%>0*r-!rHl_QU2Guz{wsC4x8Lg}Zk<`(o_ob6=J~t! z&f#QI^_Sq<{<>A~FZw@S{MWez$)EKcBB#!^n6z#(R`gx?#>XRA!&>+fcA+)Z3(xMU zy5V;AaP%*^zajP?Gxq;Kd?78)HfQm(hgRkGKG?NkF?^7lRP}W6f5zwf$MYA|a3um$ OCWEJ|pUXO@geCyJRZl4Z literal 0 HcmV?d00001 From 5ef08501a514709a1f972811c019ad11bc3045cb Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Sun, 2 Nov 2025 01:04:27 +0530 Subject: [PATCH 35/37] added shapemodes cases --- .../src/test/shapemodes/ShapeModeTest.java | 335 ++++++++++++++++++ 1 file changed, 335 insertions(+) create mode 100644 core/test/processing/visual/src/test/shapemodes/ShapeModeTest.java diff --git a/core/test/processing/visual/src/test/shapemodes/ShapeModeTest.java b/core/test/processing/visual/src/test/shapemodes/ShapeModeTest.java new file mode 100644 index 0000000000..b2c8c7efaa --- /dev/null +++ b/core/test/processing/visual/src/test/shapemodes/ShapeModeTest.java @@ -0,0 +1,335 @@ +package processing.visual.src.test.shapemodes; + +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("modes") +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class ShapeModeTest extends VisualTest { + + /** + * Helper function that draws a shape using the specified shape mode + * @param p The PApplet instance + * @param shape The shape to draw: "ellipse", "arc", or "rect" + * @param mode The mode constant (CORNERS, CORNER, CENTER, or RADIUS) + * @param x1 First x coordinate + * @param y1 First y coordinate + * @param x2 Second x/width coordinate + * @param y2 Second y/height coordinate + */ + private void shapeCorners(PApplet p, String shape, int mode, float x1, float y1, float x2, float y2) { + // Adjust coordinates for testing modes other than CORNERS + if (mode == PApplet.CORNER) { + // Find top left corner + float x = PApplet.min(x1, x2); + float y = PApplet.min(y1, y2); + // Calculate width and height + // Don't use abs(), so we get negative values as well + float w = x2 - x1; + float h = y2 - y1; + // For negative widths/heights, adjust position so shapes align consistently + // Rects flip/mirror, but ellipses/arcs should be positioned consistently + if (w < 0) { x += (-w); } // Move right + if (h < 0) { y += (-h); } // Move down + x1 = x; y1 = y; x2 = w; y2 = h; + } else if (mode == PApplet.CENTER) { + // Find center + float x = (x2 + x1) / 2f; + float y = (y2 + y1) / 2f; + // Calculate width and height + // Don't use abs(), so we get negative values as well + float w = x2 - x1; + float h = y2 - y1; + x1 = x; y1 = y; x2 = w; y2 = h; + } else if (mode == PApplet.RADIUS) { + // Find Center + float x = (x2 + x1) / 2f; + float y = (y2 + y1) / 2f; + // Calculate radii + // Don't use abs(), so we get negative values as well + float r1 = (x2 - x1) / 2f; + float r2 = (y2 - y1) / 2f; + x1 = x; y1 = y; x2 = r1; y2 = r2; + } + + if (shape.equals("ellipse")) { + p.ellipseMode(mode); + p.ellipse(x1, y1, x2, y2); + } else if (shape.equals("arc")) { + // Draw four arcs with gaps inbetween + final float GAP = PApplet.radians(20); + p.ellipseMode(mode); + p.arc(x1, y1, x2, y2, 0 + GAP, PApplet.HALF_PI - GAP); + p.arc(x1, y1, x2, y2, PApplet.HALF_PI + GAP, PApplet.PI - GAP); + p.arc(x1, y1, x2, y2, PApplet.PI + GAP, PApplet.PI + PApplet.HALF_PI - GAP); + p.arc(x1, y1, x2, y2, PApplet.PI + PApplet.HALF_PI + GAP, PApplet.TWO_PI - GAP); + } else if (shape.equals("rect")) { + p.rectMode(mode); + p.rect(x1, y1, x2, y2); + } + } + + /** + * Helper to draw shapes in all four quadrants with various coordinate configurations + */ + private void drawShapesInQuadrants(PApplet p, String shape, int mode) { + p.translate(p.width / 2f, p.height / 2f); + + // Quadrant I (Bottom Right) + // P1 P2 + shapeCorners(p, shape, mode, 5, 5, 25, 15); // P1 Top Left, P2 Bottom Right + shapeCorners(p, shape, mode, 5, 20, 25, 30); // P1 Bottom Left, P2 Top Right + shapeCorners(p, shape, mode, 25, 45, 5, 35); // P1 Bottom Right, P2 Top Left + shapeCorners(p, shape, mode, 25, 50, 5, 60); // P1 Top Right, P2 Bottom Left + + // Quadrant II (Bottom Left) + shapeCorners(p, shape, mode, -25, 5, -5, 15); + shapeCorners(p, shape, mode, -25, 20, -5, 30); + shapeCorners(p, shape, mode, -5, 45, -25, 35); + shapeCorners(p, shape, mode, -5, 50, -25, 60); + + // Quadrant III (Top Left) + shapeCorners(p, shape, mode, -25, -60, -5, -50); + shapeCorners(p, shape, mode, -25, -35, -5, -45); + shapeCorners(p, shape, mode, -5, -20, -25, -30); + shapeCorners(p, shape, mode, -5, -15, -25, -5); + + // Quadrant IV (Top Right) + shapeCorners(p, shape, mode, 5, -60, 25, -50); + shapeCorners(p, shape, mode, 5, -35, 25, -45); + shapeCorners(p, shape, mode, 25, -20, 5, -30); + shapeCorners(p, shape, mode, 25, -15, 5, -5); + } + + private ProcessingSketch createShapeModeTest(String shape, int mode) { + return new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + drawShapesInQuadrants(p, shape, mode); + } + }; + } + + // ========== Ellipse Mode Tests ========== + + @Test + @Order(1) + @Tag("ellipse") + @DisplayName("Ellipse with CORNERS mode") + public void testEllipseCorners() { + assertVisualMatch("shape-modes/ellipse-corners", + createShapeModeTest("ellipse", PApplet.CORNERS), + new TestConfig(60, 125)); + } + + @Test + @Order(2) + @Tag("ellipse") + @DisplayName("Ellipse with CORNER mode") + public void testEllipseCorner() { + assertVisualMatch("shape-modes/ellipse-corner", + createShapeModeTest("ellipse", PApplet.CORNER), + new TestConfig(60, 125)); + } + + @Test + @Order(3) + @Tag("ellipse") + @DisplayName("Ellipse with CENTER mode") + public void testEllipseCenter() { + assertVisualMatch("shape-modes/ellipse-center", + createShapeModeTest("ellipse", PApplet.CENTER), + new TestConfig(60, 125)); + } + + @Test + @Order(4) + @Tag("ellipse") + @DisplayName("Ellipse with RADIUS mode") + public void testEllipseRadius() { + assertVisualMatch("shape-modes/ellipse-radius", + createShapeModeTest("ellipse", PApplet.RADIUS), + new TestConfig(60, 125)); + } + + // ========== Arc Mode Tests ========== + + @Test + @Order(5) + @Tag("arc") + @DisplayName("Arc with CORNERS mode") + public void testArcCorners() { + assertVisualMatch("shape-modes/arc-corners", + createShapeModeTest("arc", PApplet.CORNERS), + new TestConfig(60, 125)); + } + + @Test + @Order(6) + @Tag("arc") + @DisplayName("Arc with CORNER mode") + public void testArcCorner() { + assertVisualMatch("shape-modes/arc-corner", + createShapeModeTest("arc", PApplet.CORNER), + new TestConfig(60, 125)); + } + + @Test + @Order(7) + @Tag("arc") + @DisplayName("Arc with CENTER mode") + public void testArcCenter() { + assertVisualMatch("shape-modes/arc-center", + createShapeModeTest("arc", PApplet.CENTER), + new TestConfig(60, 125)); + } + + @Test + @Order(8) + @Tag("arc") + @DisplayName("Arc with RADIUS mode") + public void testArcRadius() { + assertVisualMatch("shape-modes/arc-radius", + createShapeModeTest("arc", PApplet.RADIUS), + new TestConfig(60, 125)); + } + + // ========== Rect Mode Tests ========== + + @Test + @Order(9) + @Tag("rect") + @DisplayName("Rect with CORNERS mode") + public void testRectCorners() { + assertVisualMatch("shape-modes/rect-corners", + createShapeModeTest("rect", PApplet.CORNERS), + new TestConfig(60, 125)); + } + + @Test + @Order(10) + @Tag("rect") + @DisplayName("Rect with CORNER mode") + public void testRectCorner() { + assertVisualMatch("shape-modes/rect-corner", + createShapeModeTest("rect", PApplet.CORNER), + new TestConfig(60, 125)); + } + + @Test + @Order(11) + @Tag("rect") + @DisplayName("Rect with CENTER mode") + public void testRectCenter() { + assertVisualMatch("shape-modes/rect-center", + createShapeModeTest("rect", PApplet.CENTER), + new TestConfig(60, 125)); + } + + @Test + @Order(12) + @Tag("rect") + @DisplayName("Rect with RADIUS mode") + public void testRectRadius() { + assertVisualMatch("shape-modes/rect-radius", + createShapeModeTest("rect", PApplet.RADIUS), + new TestConfig(60, 125)); + } + + // ========== Negative Dimensions Tests ========== + + @Test + @Order(13) + @Tag("negative-dimensions") + @DisplayName("Rect with negative dimensions") + public void testRectNegativeDimensions() { + assertVisualMatch("shape-modes/rect-negative-dimensions", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + p.translate(p.width / 2f, p.height / 2f); + p.rectMode(PApplet.CORNER); + p.rect(0, 0, 20, 10); + p.fill(255, 0, 0); + p.rect(0, 0, -20, 10); + p.fill(0, 255, 0); + p.rect(0, 0, 20, -10); + p.fill(0, 0, 255); + p.rect(0, 0, -20, -10); + } + }, new TestConfig(50, 50)); + } + + @Test + @Order(14) + @Tag("negative-dimensions") + @DisplayName("Ellipse with negative dimensions") + public void testEllipseNegativeDimensions() { + assertVisualMatch("shape-modes/ellipse-negative-dimensions", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + p.translate(p.width / 2f, p.height / 2f); + p.ellipseMode(PApplet.CORNER); + p.ellipse(0, 0, 20, 10); + p.fill(255, 0, 0); + p.ellipse(0, 0, -20, 10); + p.fill(0, 255, 0); + p.ellipse(0, 0, 20, -10); + p.fill(0, 0, 255); + p.ellipse(0, 0, -20, -10); + } + }, new TestConfig(50, 50)); + } + + @Test + @Order(15) + @Tag("negative-dimensions") + @DisplayName("Arc with negative dimensions") + public void testArcNegativeDimensions() { + assertVisualMatch("shape-modes/arc-negative-dimensions", new ProcessingSketch() { + @Override + public void setup(PApplet p) { + p.background(200); + p.fill(255); + p.stroke(0); + } + + @Override + public void draw(PApplet p) { + p.translate(p.width / 2f, p.height / 2f); + p.ellipseMode(PApplet.CORNER); + p.arc(0, 0, 20, 10, 0, PApplet.PI + PApplet.HALF_PI); + p.fill(255, 0, 0); + p.arc(0, 0, -20, 10, 0, PApplet.PI + PApplet.HALF_PI); + p.fill(0, 255, 0); + p.arc(0, 0, 20, -10, 0, PApplet.PI + PApplet.HALF_PI); + p.fill(0, 0, 255); + p.arc(0, 0, -20, -10, 0, PApplet.PI + PApplet.HALF_PI); + } + }, new TestConfig(50, 50)); + } +} \ No newline at end of file From 29054742a0d0dd5029d258e8d7440b9e3bc208c3 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 4 Nov 2025 00:09:09 +0530 Subject: [PATCH 36/37] changed resolutions of some tests --- .../align/single-word-center-bottom-linux.png | Bin 4944 -> 5894 bytes .../align/single-word-center-center-linux.png | Bin 4957 -> 5910 bytes .../align/single-word-center-top-linux.png | Bin 4944 -> 5894 bytes .../align/single-word-left-bottom-linux.png | Bin 4394 -> 5681 bytes .../align/single-word-left-center-linux.png | Bin 4399 -> 5692 bytes .../align/single-word-left-top-linux.png | Bin 4394 -> 5681 bytes .../align/single-word-right-bottom-linux.png | Bin 2858 -> 5622 bytes .../align/single-word-right-center-linux.png | Bin 2837 -> 5632 bytes .../align/single-word-right-top-linux.png | Bin 2858 -> 5622 bytes 9 files changed, 0 insertions(+), 0 deletions(-) diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-center-bottom-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-center-bottom-linux.png index de4172c4881f5e93b98bec41191b2b2a16d9e7e8..11937e157818edfbe407d96addcd9c502c2e2eec 100644 GIT binary patch literal 5894 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 JdCHIc{{y^Rq@w@; literal 4944 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 literal 4957 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 literal 4944 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 GC;tbI9yBTd diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-left-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-left-center-linux.png index c0508b64d753e46f9dc4171a5125ad05e0a3d312..f736f6c20a6df95e196696fb01ae2bfce67f7b87 100644 GIT binary patch literal 5692 zcmeHL`8O2$`ycAoq%dwu*$3Ho!o-*i<;qU95SOwfc0V3)GagF7fw=6Ygg+!)Mz+pwK^eH-RxX00hmDX}g=!unTFx4i=|9UTD)HyCqPRY8Hc zs*{&qN_H3C?%dhl#IKEnWEv&E2x*X#lFD-F{+qF6I)d78SWtwH_zeg4iWA)^#f@oK zB}$9;Za1P`;@FbVlVe4}nJ8a+CEcH{%kFJV|N2&cDHNgNP8EMu=ti+*d+&9cu$V=x z{DAuHbCNxRQ|e6bNl-<}%g};?g2QaQaZyo%jMtOhxi{3QZTd=zinWV|s)HgS;KxaUUTELp8nb`{@wam{0k#PwA)S$5_7{;8!!ZLDHuKdph z_0sEw&h)M2wl+|d#usM<0v10}OT9A<;=fkmcdgt@-&&Vq?AItBRYjVQmEEZ&)Uw1M z)7|R+i?5SR9>K=-KktWY;;E6bDi#qH9UtroAPwDf9m{iYa&mHXWaClEw)_0pvGQ7h zBSxrUyd_c0I7I`$vz47Ky{>5pwNsYebUg}s|5BA8At7OJu5W0_x|$ao14Olg}0G?fP=!fK)2&g)cly~4d-(kZ;r?C=L!)P zkE5$^T0*&Gu8a5<@~QhaU`xVaHO5e;$ZPN50L`&+MoGCxq81At|r# zGdNnz0%H@v3gI?TI8=cf#!e*9QAi74-;#wRH57d&+gDb6NH)nBP-J2SG@l>U zPE%*1HMwQiI6?XL@^}lTYhm|$Bnq?p3AMcv9pGn^OIQTG9v|A8Ay)0w9yg}m?Y+Tw z&PU#sW%Rd?%)7ccty7F&P&`yt!NF16^)<4!Folkqip!0z^HT|{xUnSZK?R}AHC%7*SS^hsCkb! zALWuA%jMP#@Sn#m2QoU>{*x?Z$skAjfiAAg)uc8==Q-QEHS0+@gK zh2`^X-ni@^f2Q>tcn{mWhATT|-up4lSb|Dg{9WiXWrl^Sa`wYcju+Jl3{UM%ayE=# z-PfQzJE6amR?cHTG|-%^gm0a>e;5^vvMr_9D;so!0XxIaVH(VGf*Eg^r}gMisfTn4 zjE(PHm(8iS&xLiH4%Fo|JkD#+mDwpVKD>veRV-oDNw+TSgjJcZ+t6>ap;%WboANaP zamg#WcXD?q` z^d(Qa#0R)^l56x|qds%2;zfqn}pgJ_vLzoJE9i z^rUoN?5T17w#kn47AG0eNPeWt{QDSj(;9vio|S~NuC=!`!tOu=gE4MiG@m_2!fLNg zq~Z_lrU*+&+y?iRt^8=AVP%yIYMZR?XJll>n4>uD#wZHP$QZWLel&`uhMsxnCUqq@ z#{1QAF(aS30gG%FPb8sgXT!DsgTvgJyE8q{wxna*G&c>(jnL7ehG%y$!jJa;(v>1-{?1_jk&lr7a*OJmMg5x#;bSJ*VL{NN^S|<- zoefeokyHU;<>6h%Ot0LMtLKDNYfcWp(H?7i+fi`~6^sEO4&)-hX*&y~pz zhUYWxvwNx&QWK|q`$a_$bqeJ>8^;@_$H8v*>4ufH2H&2N#lg7u1gtmrMoOY<)Dn%1 z+TB_Pe+AaOrb$+m+#6+MCjO3BrH8BVRlFM^YunCWBKT-JI*BT7A7VGDY-#=m3jka+ z9sf_ZhnFiNe#NJ1@GWgaSj^X88{Rh3E`a?T8ym6ig<6+l#5;(#Rq48sO+V~v3yQjl zwzft(KBKiqaHaGgy|&~uUeO7AO#Rc-7;)67?TkJ2Q7UbHeIk{dw%GhW+a&E_ML=yR z2S!{JAmBGYH?MV{hSdY&1{~U_qLPy=TE*cUD%yzYY4;a_kC$9 zYT=+s+#9G}?qHc8yMU&~lD-6EwqI#DHZ#o0l?O;+QbS|~l`ANbU&-+)@>N`%|HjH~ zL`y&~-6I{B)HJqHO&Ch?{4)c4> z{ctY11doswm5t# zgmGz$|ISmT;x^C_$r1gAx8`A4aY?|ru@NRt(Fu5S5n#@;g-WZT4DWJ!Jr8E!_qHBJ zkCjK`ldV2-;0Q^Do|HE>b_>A~3>a$Qu$KZMQgh=k!ZTR#&Kn!sR6e>4QxjKO-3u@*K#oz#SJVR#W!xm@;!C`)WQ5 zy>y#Fp?wK`(hB>CI|qe-lTbxF$8FaPaXn4+sJzCu{kg-P=By^NUo6oeW$ra$O`~}H zevCDs$du@ogtO7d`8!Ud{q{$VW(H-YJKZ72NF1MsD@FK9>3)Xy|5KSiiC z_op}DiGmpqab1&Z^zU*$C?$dTDSIvd+ll@kY=w!L7hBV&)%3chv z|D@5Qa{VG3E%OS|hQVQdYy?Qete)-K#62#>WVp}jKHbg-djjWa?+RV5Hq|YAnLZLV zORHS8qErTM){Z41yvIy}t{1sD2z;u8?Jy`Fj10T^hh(_i(sX*9yeFbY|WW=_5Le}7%_N3pBpQ-bJQ(#oW zMzH?Bj?O_U{vv>iD7Fm{!X(l#7>kc@( z`(^Nz!^kh6od-YOJ%+beO$kwBaRC=y*uSVHRO<3yCVyXkzd2vFFz7e@Y)SW_Q)|*` zR8T;?qC>6k;!3A6*);rDk{~OJu0Wv3c#G5ORAy=uLf_Yy(ePZj?r%u1cHGOC7Qi^* zo_Ng_^*Y|V)zD!Q!H+x#(8c3X?nNK1TMEqo%8Mo$?VAl%0%NY9syaPXu^Fdu(-KU; zo}p6r1!_`#5Cdp?rk9usighy*X;~S7#pzW8|K~T|C9(cM`d2;I-P|r-x)gTRKI7dx z_klt^g9Nz-^@Z}zzf9Aq3UuOKBoG@ey23EAFN=AAlAcvPVOPc4tu*>KteZ0LIh6AT02+e-$zfjVXY&t&OqVEzSzu%19^|!{Ml)BzjSWksQX|5E0+Alvi!=DhHspsa*fuNQ6aAlBEOd%BBhz|qa4nqqfIgQP++g3cxVJKy4=Mmie;ny9@~c}$ z#VYt#zl|GZzO1COGa@2|uDz{6jJ~I*m1ZA+Dpbo=Z7JUA+av8wwNK5cBCvfvI?T?t zW9XCwO_M3!CzfD5{|MLzKDrNn{O#yTgE)CRAeoO2u}=3Ahwn2T0Jh_-LsJZ(7}{o8 zHMzbqTI@>Bb%)9e^0oxP8*Bhw)Dkby2Hgl=j%eUh3s%dZM9FU-w>MYuD;>IkFaqdx zT$`&4yR!vt34GYuzI@R`z<4TXyc3{%`s|JN`$f3UvD(yco->idqf6kS$Cv|sh4r*z zEt?&{ViM%x{%+oGG0LP1u>bt{c^E)V80_N&*br$=FAKEQTzjMu2M_LT%g{7jt+&`y zuq~0{f>2<>PeAQ98TeiHST)(qkYn*?X*J@ha;u8(4dTBk$XY*?1J6#t8^kI5#jB4@nSt#+#-1q7@+V<3}AD2fNua>&}>J#ZoYjTCU)%J z<{XKZ_COvAzb_<`z;#UNL5xRJmQ!2GQptIL0_;4;?}kue`DeT;cdmlHOhXpN4?k!^ z;6(*#oIPuy!6$#IB~jJ3;$D%4P?P`~`nq8q>~&u|I}aZ_)ed$Fz|rK>g(%UmHX$HF0ayJZBsO`W=03t{(Wlu h_tEix-&(g(h(x>Q>+qgd@G1<^yI`b~i~jTBe*n=~C;R{a literal 4399 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 diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-left-top-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-left-top-linux.png index 8ea289fa25730c925db115f9c6c5689ff95017cf..008db894f48824b68cef91531b815460e84c1fe2 100644 GIT binary patch literal 5681 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 NXJ%r7C^mAw`yY`w3f%wz diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-right-bottom-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-bottom-linux.png index 846d8de0506918bd85ab77b94316bf57eb85ed54..77db6984e9a439c996b1be9f2394ec6c6d35eac4 100644 GIT binary patch literal 5622 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 D??nWC literal 2858 zcmeHJYgAI{8aD01lx3J{YC_5h&6*g~yoAXrO=>7imrY}7Xvt_pii#m7!c?TDQ;tq) z7s*jGkExiMmq4M>%1d6y`vp)GF;P)dK|z^KbJl78p1)_!UTgo@@7nu$p8dR+@4bB* zi{8A^WTTdr*5(t(-MqB4*6h>VAZ;MB2sO6Q(%LwC!tIDR<-!y{B)Cfqt(z5Q6zr-C zD~!xG-eYwiroU7FK*}z!e2@XrochhUVdL-pI)uuU(y-t7&JVX|9j*X&8CZnFITVJ;b5G^-*xjh~&T*(yKJ}NO(`o|P4(JvzGuML#^IaP-Mzs^NnyHE#Y3SE z1pFT^x}OD?I-;J#ab{;IaNXXS4$6$9uglR-jCnUz0!98>3uxm z8|p2?fS{nf*9y_Qq^Z;9<~1|h&oQd2t9S3-z50$oApAGha@P!*n3xz>>>M>ch*GOM z0CEdm+#2`s3@6gFva*HJRu@dJ*JA9tI8g1`2Vks)+YT2C-JRgLWRMP;F$}AD0}fDR zcNLsP;J8$AA^jzn&_O|xJEjhkon}`nU@CDs?wYfXjt;qIL=SkYkE_?9tohkV4wVrouY-~)75=URb=aCLLI#T9F8~ixr>iIt~w|kB-hHae& zy*xeJ+uCO2Li#`-ax@g}cYA4BWkJRDAhWgNOX z@tP!Q{P3Z=qrzD=Q;SL47rodQbhhyO;iIRNo)Ki3Afy^J3GPOOp~VQljxv19NL5-I)LOm`BEMgBo1EbRv;EIk5+E-#6)r%w zG3opJvNA@a8O_bjWGlhyin4iDcMZpqwi<}XixoIET$4L)FNnVU(ULB0F~U&;;v-Te zU^X%|UUX;0%C9o)_bn^wOYQ3O#A>`H5V{#!3}`bl9UdC;_wjkiTL>8_*Km*+$Pa zgiJ{x)S{-TpPC)W!Sz|L@lB(>nI{6%Xc~3ns96YI6;7A7I)D24F!NXH-4|T002??0 zO%};81VRA{Xfr$Ia0`6p5YX+b=SnFP`FWEcHJX5waGCY*QIL1SbYL{_Ph#7qG%_ZC zwEZ`WRh~ymZ3WZj8Z5~crfP}-9cNDzT!CATFKU3uWyUAtK(=~$Vhz!UBm_4YZkYhu zVaB$1xRTy&RQy=g@>1{DBzJX8nGvZX4M#Y5X00VbiVEhMf4Ri#neI<3Wo2% ze)y~+9Y^bfK0ZiDW7Y#BFB!B=_b+@529t#P&dV#c7BP|sv^T6~ZSCcyx*UZvVrJ-w zt+D*lN+2N0?Sa4J*Po@XDn|5dflk39@9BJDz*iy0pkWB#%CNAowk^AleF6Geo|~WF zpc@q#*_-wB3(~z=u5T5kz3TKaJRVOZ7BevnP_2UiBx|aei#yp5Vmb(^9Zd*7wq3k1 zZB?i-#>Zk(Dpii>BtN0fHMFACgIvVfU!7{atq;a5ULgevs-l@pCfSZ@C*4&ua}2er z$Wgz%IL!kJS8{WMp1r+2oA(rbVR??lZR@DHwaLh`g1QtD^U?e^`&+w=G`e%6Vb;j{ zX^zZ=WNmV-??EzZ)p1tCTLE2@o0}^Zi^tT9)0_hE*t5dIrVH1OXS4J-YfO9(&gu{d zLZ9q3`KlXmuyo*#DLp#IXG}Oh-OFXND8hKTm<|WE_UIJNKbM-jdj%j#-D@4_)P!+ diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-right-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-center-linux.png index efda9ca97b34e75851ea73485df0e7715431959f..a1b1fb011438b3e7e59b3d87612c8c9ad7030c46 100644 GIT binary patch literal 5632 zcmeHL_ghn0*NvbUloCWmx-?OcB29vnF`$Trrcw-L=v9Hx2~7zgM7mO>4Woic2O)H% z6Iw=)8j5t05C|Z>yeBv_-}}z<{Rxv_lHBLsbN1PL?X}jvVcMGNj3+owKp+rCjazEE z5D1L{1ajmulm=XhHhokNft+d8P`m!X%Vcr%WORhp?k9?8f;)ci5m~4x?%aKG0XwX< z;5+Q~jo8gO-HIEX7jGDD+(w8>U3nhgb8AmH4$b}+Q85>f#GElJJ!pNhcrY}c=DJ=z zS8X%ix3rTaeXqoyP(?y_ua1_W#Wm?5kZ|}4Ed*kVfF6N-U_n3v?J+ROZybC_A@6Vg zd+Fay{C}8$vlrC12cKk1ml*dIaC!|{!NyHEPc$?ybrhPdWYV9nvmB$TaGsbP5V{lw zquHPI!~_*K#$Vu{f43fg{q*#}!mz|TA|+4+16xM!2QN1S zpG4xGDEV#{?$TQ2*KXUlN?Eipz>ho}I1APhdjRc{`)J-AK2&0(m8#%A*%W5ok){%` zpJ3`=;X2oSCqW!PA001l?9(MXVEK3t+_Jq;P&bX?z({IU=8I7`354YvmSoxx(n=i$ z$~GJ_42sMdIps*BRU299{yy7lcHYlZdU|?9^m1(_cr!Px4|t&y1eF6LiL86ca`=o} z*;QpQ#3v^>qTF|DWwtAsh}M=5KPxs|;cDZmMOmslD2s}Jyf|EHuF(-MHWSTp@D%2M zu(z?UQZJchz;xsGXlXFW#*Q#%etv#$`v(?_WkqXme$X%AA(4m zW-Yd6$CTxu-&DrPe$ZDNQ2_Wl_P!|qqFxvJ|_0>9wc6|=r1spP?wehH%F1` zmJ90kMGoYJwGf*d;{gRGH9jNdE?O#kYc(5V=vHpeM2X-n{uf+u)KIY%$PDUfmN|EW zD0%o)-~6A)a2%^PjP$DSBRetIZOtYR@m}S;4L*$_jCleY6bc0_d+_05PVQ8N^L)>{ zBx##5uc?TY^7u;k1w}Ji*&8a(GBXr`IxZ2V)xJpP*#xy(<7ir-xiqU*nc z2*Y9*#_Q^c>U$(2uE44ze#PK=B6t7DG5jN2xn5FKs|;qA^dR48$b<(`ftr~mq)RYQ z1k-Rf>{gdqdbulr8sIvr0{vWN(Un{jL=!T(!Zki-OW|updP8~^>G2h=O#8kM5930i z*>{tq43{n#WND_7m|Mg#pa#WkA@i^FO=6CRKYGe&R%?uuQdHyyok^TQ%dIn(YG)us z^>U-{>tMYcM2*UqyHl@qBuF^r)sluC3LX!Cv?G>1MwZ%JmDPP3EnL$4Y zl-l>jVx{}u>%6&vJv+-PYuED@1Tm=vdaBB<_rgv|>qQJL9YdkM)fM3!@738T+&B`^WjHR`E)m zXw^iqn*(Q;w2o8BWa|blLEM;Q*+%|CG#-yX!W&E0TSFofovs(UOx;}N#?=&pxKS6g zwtp-TS6y6Op7MT)?rp}h5lbCxBswR(pc_uZLDTSa| zEj)5O-qS4b(@jrlk2j^#wl2V4Yw>w}mkfxqB0GP{(I=QBzI-Vd%Kz|_>bWY1J9jmy z=ULZ+MntLXZ+^(hFLug7fDGvB>hkY++r2~*pR))|Yde?N5YBd}I${aRJGn}BWfe;3 z^|p+!C>7fVBCK|Qdtu%AmB>TzEdh20WI-yO)jR-%FCNOT`#&2U@X2_PN4$3yam1?w zBexU8N3*oj{dU%ePOhGw=`VXsl!Kp6VL#ge7)msBU&vo&xYFGL&e8+go-`9Vz;`-o zesQ26Jq0ShF;MSYAn=d%;{`D6NfKve0kJnVskyv46Mp(qPmX>8C`Q;!y;@0WU%oL? z6XUirt%0tfG%~BSW!$|mXO04rfVwC#>9k-5?NXwntbPaRd&G3iM$;-u%8E3j8p`xa zmE}?!wK76_SwTRh;`lma)6$d!_IH6g4B?0)?eSuqK5^}5yZZZU{m^ih*dZ6vObc$@ zUo=$eU1nxD9!P0hM)mW8*igRR-BrS(hQJ2xnks7)?TLed^o%ppuol3NIfrFG#$;9o zfXU1*CAfCy65wcALP8cmHK&YibV?hxW`w@>9y71<2oE+YN(H^1%hleoN>Fj)ZEA!? z@~VubT{bM9KB#o-y?1p;-L^L~VwdZGgYOW)7y^d3tmT%&A$P1D(QBX8RqL|-WRS#f zpWn==nUH2Qm-F(R#%tFN0C!BQodfL4xO1K-L|ahKN9@tZ=yU=8V{lASRaw(_H>zHl z1xpe$?zyy)r)-v+8CO ztJYxLXNUgWq<==}%GPih%O;WkAUn4FVZvoYsU{1X--m_~%*QPc*Wy-iD{Or&W5+Mt zAnCX5Mh$H_Bb29D_)QicCL@%#IN@6Tfmo3Ol|7Mye4TB+-XC7eI)rnU`O2DMb4J_y zoPSwI32*jaKn#AgyANjx0i4&S{5+KJG~tK%gZJ7=-$vXAj8|OM)NG&@KWQDRYMZ#% zFvj)*=~VNOX|JR2$&9`Hd2|J+ZIA$xHnDE!VX zRDIX3z0cW@m~t5X3QXLj+Q9Jy-`L2fzw3r_K-T(JoFn(=C3Bsa1dKa z8+GhiF?)i3w(bm&Pn38-Hlx{3s+>(xaKG(;nE_b40_}`vH3Fwa9@q}#8>7gk`5vPB zjYoz$QlpW{I=Cms=)ql8ASwKzG~SD)1EgTXYDTl0`Ey?9x{|L(eHHVl@qTV`p_H%l zdGnzuN&)6wY4d9kum7f5qRJ^nPqGmTg$g?*)a0=|;!42l{2y!!@)k=7%B-uirW-Q} zrtR=MK0e!`1=aj|Run~yot<4@jy^8o+<9E0Y5f|GMj$1{S2A}7)sRFPe)kW9uv!eu=upUyhL@$z=pZz;@uh< zucj#bJ>nrw{)Ga(gM+k!`**?kbH^{q26 zXpepQQU=%$Q~#}I|Lr$h2Y}|1rR5z5C0%WVl$Dhoyt-51{d{j?uVDZVaOIxBOtz`o zpneGbiMXlZNy=fmP-8Czs9HT*<=H_yH~m*I^cD`+eAaBil+pa*@+(n&Ii$xze_mIB zYn{XtXlS;o0gh5C9gl0$zq|cP#I)8Ifq1qX6woY#iMez?^M#_@1$|TgpLLYX7h@kw zqvGe@nDVH7Z)R79YUs3%t4k@ef`h5egp9$Jf{k*(Bw=o53tSx$cy7{A$s%*hMW91b z3oH)Ce3<&dj?`ta63<^NHQJD&AqNh^e?xf?Nu*4*Uh^Pb?5DFJ3s~>z$qETI?|YFD zwkh&1zk^8zOqpd-ut+*a4#$yl9UgIAj3oBzp$@rrDrJCOfpV6Cp=)KG<5uvAGYX#M zYPb0w>dY5(Z0@hrxRjJyS^8&<*ZS@JT7@(+8UY6Ji3-{gh~N4zcU2Ru0672`qMzcjnHK_+3FYaHvQ_gs#B5u-cGTrbG5Knr zRirJ|(6cnyGIGiP!DI%!^z=~}4X`vfp>O(t8@`57@>z>!{RIkifRpb<`_}5*omV1O zq0Ru*C@AzPP;vo`4htbwQqF1Ya%dooIz!E^EJoi^ODLf|u6gmkb>ftzY0JxtG3Xa3 z6SNcF3IaHERQL)^Hw0tr*uWp*)wAz+P~R%%Ox#nL?fZ8pGD7#c$XI2%I|AVALIqt@d4wsJ5eNlh}=vY+ZQU)Z8rlWMs3M%``>Pdj&KrU_^I7lfZ7h zE|RwCCXLmg*GWUE*F343v>xjSJXNit4CJ>@0vNNJUlZxy?rLiqS9!c@Jq9tNhngAi zG1SYTKz+c_wNefV6g|RMy_?Aj`jSQKIK%y^mIz>5MWUdPaulo~V0UXSJv%!a*e~kv zK$&c|qFe@}*N=sPrT#o(`zz5I9SB69195JUI#}f!S9wt;!F14-ctt=+$cj8tG21=v wIqv5PhD$gjq}=@9_h$dTF8t?<(>>me3576y_BmVdW*4GyLsKmu@$0ky0J+E{3;+NC literal 2837 zcmeH}YgCfi8po}sVlp+-#5APfm^PU>YMG&uhLV}4nHpXP3GM2o)Jg@)7{x26%jB3M zX{mXcz|c$q5j-!Rb=I7-*7-c2X07-Au=jfQ`|SPu|DR{S zDIV@vjZONS)YQ~8T#jN+s;R9pRlT6Kz|10SJ6ug|(})Yk;ncODnbC8!*E6t=xv@cY zw=-Pb)Qc%_7sMW%V``a^RuPD}$M(H0%NfV?IEm4@v+*kz!~0v;x|cq8NE-9c@4O^! zSI%=17-Kf`sMeJ$6%&fjw(J~P5Sk&&tIM_|^gv1LcW&cbt4UYjj)&7hYkq`(*GAj2 z1@YJ5&tzb_w8TY^Byt#sh>)~MOfVvReC+Y4vTnG z;B*SVES-a)$<^92DyY?%_O!aXdY)Sm*bth`^7irJJ$AV(UQezw<-i^S862p-l|c)L zD6;_ePsuES8PUu>^8I1dy%RL^1eq@0#U-Ww8q=O6@~~GW@uzZ`=*qx`TFTsq4s2F`)d}0Q(8jYv+2)eSY!$;Whk}oWlg}mAJ2z9)R3tEByzq1wG zMqoAdtM-dWB4h)wffxKt(CVxtznm?MJ{)D zr$LZp#ZnDJ;iinHzRt0hAe%1?n4!k!l6}klyk)VH-m%{ZSZdepR6vcBkxY4DjWC zHD5*>2G~&q@i%LLk{Eb^V{gzh6gj%dJTC(qK2vE(c#*h%F;eIEGF%m9MVT_-KpmHV zYIuYUZjPoL87T4U7<(2UnQKoPf|VBKX7tW+>_z=0Z+jRhm|fhN%*N~Up{+?8laQj^ zBDWl}boRyOmGc0j$%pc`h6LH=t2j$sVayi1^yD(_LtBb=jF7hg5$EkBN(}?I*6+X@ zNvyLv5}p2@>#JC^;ErMQldqLL21&li z&%Y=jQ@xkl)AssSu(__v7f};0`T0yYru`_h62NipTnTi^>Dg*>?6k6AW~9D~vrtd~ zN8KymLGL9oWJ*Om3PR}q^+zS2SMERjJzf5ahw*POzWs>@P;iCjSCJKQ6d@q?wS~{W zKwllp#Ii37j-hlS#P6CWuRXXis*R?wh+X%+DT-UK_*w zhKX2{ca^Y0zzc)XTUPpVjA47dPqRHOP$m5V$727wa3#;!J@>3J>rNLqQ-A*} z_s4>vSvm@W45~h|Efb0g-Az$sW8z9ws-`KmD}ka@@{%>7=TC$Ii2~S>`HX+Ip$*%| zvN8;;mk1jAR!?DHj-OOj#Z=|~TWi*B7<`NC6o3ZG{lknc=IGct32{-x4`n?d|J&y| z&K$Br^r`IQW$JKUM4fiFwBwOfZV~hOlqJp$t-@GU>NXKUGgm|&Q_h(vo715v-f~MWZx1;UCJ?9hE}!OvzJ9Pck<&|8aqJ^q{iY>DA#!c6v(UU!UnJ@_6uub- z-mT}}xw_r$mZ4GtU~rZ;A80~geB$`M&H%Q%lg>7+M5&O^$XN^4*wa8iein1#%40%Drje0Rtko|%s$i$O@X@cqKHUll z19dfXhYjl@-$IfVEpQE1zJbJj?kjXz zRlwnlBh)B>?(gtucc%R%cs{Ga1qHcW)8hf4a`ASQG-*%+p>Qf&tOMyLLKLZ|H&l8vMx& b6zU-#+mpK!g1``GLq#;WeXuQwz2PP z9oYuiW<-p!P8iJl(0R{$-uEwfe>n5wcRt_yxu5I4?(4qpZ-l5ShBxtLPW(Q|BfDq*`yr5Im?q?r69xwPogdB&$-V^L z2g&99=g~h%{Qq-e`p#j2=8!|Y=`uC};%>>1<;eJn`xEs|)Q-i8<##N{YwmnyS|P`& z-?`#cXzjTF04Wfg+kr878>*yBBUfG)Zc}Tx__cS|!q(@!FV>(JzOV`oS zkz2~-BbLL|zl6byS;X1|=3skilwq>|n=dylq406MAlwS?db9W4b&A@rf*={{A8#Ye zvwXWyiEqBj>KdC*s)gDya8=nE?J^jk@Wr@}Q`4+1GhJu znwlcie)aFmGv%PI{88#W@N`0jT4l(da{-Dp^hUukkH!i@KQX@tAlUtIcOI{OygbOb zqOC1y@iAL%gy83`^yUEk@b%Hs5ExTnC4c>ZwO`j)?_zn^kutq>^^$vC79SlxXQc-! z+jpl(8omSftn^)(eyfA!Y0%hN&s4?T?M#vz@>X>2%k^BHxuKMC0Nxp2;Cu+qyfy7I zT2ZQ>p{azI|CZ*rz#@bLDX+~9aPXhab{(y_S8dx6iWbF6TjH*zsroLAlmpn71Cn9& z_kXRx!8B{#OhwX8E0i`iHs0d!V{vE*WpnO-AM17zhJh*l{CmZb>ag~`k3LseLpoZ8 zgvRa%$4V%iwV&>MiDpg{#k2wr1ab)^m)ve)S7OrR)Fhn;;6<=?X=`i8idheWGRhNU z9KxZ78zZ>Dk6ObBT!8~J*zW|@V^}P4F0q-%js@*(*wYYOAhCAuZzOGKp;dXY`&6@b zyoB115f2Xa^;>x+`JLE$f6nHpV;_p_6f#Fsd#Shg`a6|%x0yN!g;MI zJG4bhH^Pf;uab-t;^OiSpK6ukjI#AJ!bAdn05JVaR%QMh%Om9;ZiF=xUn`x_3Qtfl zUy$#-@soob-GOdtJv&}&Z(F6MrBPq9Z^%1+`M@D+^xn+<)9)7$x+%{i)sD=;DmIU7 z_@LSrNe_1MUA|Q5GW46$WJow3*CG<6Iv1_tHJ@!$Q|%e0KRG$M`GZQO-O#AnrddYv zNRx_!WmVpnABrpeS|b(H85RBpp{yyHubA^G!uz=#~Y^k|yty)Lay1Jm>l&+8mA!!jw1AsHOEixVyo59*ne1htID8BWR0# z`LBnF#cR|t#$;0@nus2+-AB(#FprCiBU$O%_%DT+*%5JQ@>L~!CC~3hVeCSUxu?>? z7QuOMuf8b8etB#q~iJgL>Rh)f*YRSC@U+g<#1{Rq}8De1<2aQV()eH&{ z!pi%ciH?Jt<0J--3Nev6tsd!9Q58#OtpVh8mi=^i<(t!RP$fKa_cl`v_^S zz#Dairm01pLeCzqeIGi+b>s+1{|&1T^tLxDqHXV(9rlc+HblCWAElIrGIsdmjkbY- z!5s~mXtc9{+QW~6tgdyzzu5VPqn1K=tv?{T6ubebN=L004qo>WsE-x9Ol&aDH7dV9 z5iH1R2gHJA;gQ9N(b##8OJqF(ibZF@`2Bt?poWUy26%MkUwipmz~<@( z1wS?);` zpSx8(5c@yzJ2>m3I(Z|3wBPXeuPXb@3SgS+%NtmlYH3=&1!&J#zh|FJFC+24f`cEL!8d#@VHFzb^)DAG5f?ZaZHfU8F38fh}5&!|n|e{C%uNcc%ySIH-SbWZZiTmWgK#0j?5%sLDSOR3#DH0Dri-!dPEn#=P(3_x zJ4WrE-X%RG6v=V$j*NFO!_v-kuUoHZCdF=+T6{u`xC{V%YS_JBe#Tycf*>0$~tH0Cs}$3L59zJEC4UK z8J0KN-gp1}&#MKVORlgNdii ze;Y*Jv!a+3gzhiKs?^~XmTtY|mv?H7kPXMiuF7K69e9{0PI$Du;B&$VOLb|4Y3E6UH&gud$6#e-%J+x}S| zV<_)wD+f2W<_u&QNnhe`0HE69#O;>BxP6ZF^<8S>rHJ|P)ss80!|}R{-yVKDlM0Xe z$@tf-6F^U!`eE~rGYh<=OdIwM0UlZo15P%Q=7&ni`qs&39qmNw8elu1vuJN@7lJ$B z8h_8BB}T-JdT7APql?i`g|aQ#w-4jpWDuHHb#%@!oaj*DP+75oJk^$#mVqwN2 zM{ek%ptsR`BxpsJ0nIOx#H>#p_$&SAmctdEbB&9UGS$C^1(!XTH2|KY3GIeVN=~+@ z@^Sa8zWU;%q@*PJbWjeP?X^9sPp7B{SX4_N28Jj{KO>kRcJ#EB&scQ;dO_rGabMj4 zF4%8y3u%&XAs)tbOwJBNkd4H{J6qQTIWir4-*Ik5+;kJ3OwvX{&)B)|P1G!{h$AuT zn=_Y10*+3{F!!WuXn4&J#)<1ZrHsGDTE}Jnhq%_3!%w-T8ml%2@~sIL-R6Jv_JTon z(&R3fEWmAoaR7#>;+X#m@NZ;?;{f<{^?*$w?~$dY7Lkr07q5Ci2^xD+^K7`J+zmK| ztQ(n^q;0DGfpZ6LXm@(BkP2>AD!7iUfAyoPt#qEb+n&%?B+)C4A3;xd9`kjH{Hp;9 z^Sfq7%1g}bPDn_&eyuXUmMqhHP{dy!EF$xAKvjizRe4EntZ*=|_{515z*yh@)}2c0 z73pGtJ!LqhZlWRVm5NjdD|b7;b=mz4&(>@byRE(9oHa@GmO?+vbaydEA1 zsUO$`e7{)zMZpOaicX{C8D>2*Y7J!*m>7HEtVLnXPK8iC*l{~pjxz(bjfP#yyIJYw1qCI8BObwa9jhkai%4FX#M3v4>=S-e z0wV0+kGYpdSf;=(7>7I$1>;u$9yJodg>dSB?`Zk~n0(=_$I98e%PDXd2?+^cuWQ9m zleacjevEpj-@I_UF`N_44vhx>Mhp|Ap;iU0cvXoKU{Yf4R)W|+1cf?^XK;N z?Xy;w9uW~SIs3@g3G7H{bg`-^OdX)qRsK+g^qb3|&69%+aLjVhPP|b*aYpoFsEY1~ zj_|UwV=qqTmrXr;5)n}z(-0C_siVmRdXH>H;6#HD7h}8IaJ=s!Qhi8d4Ysg4PVqWq zBV>$|31TFMfk678aL9RiO(;YkVDrzT{~shM42Y1G##2L`mzCgqE=2pHu2zBOt>AwF DlrRjV literal 2858 zcmeHJX;hN=9yV=Z$}-F}H6dj}vnIwgmoPb{Ne!jxv1v37ExFo|D~6N^Q<0iZnNDeo z)S>I)=+Hoh} zz+x}7ivf0H&?{_^dbLOXk@-ZuQWhUe5l_ZGSGT`u<0IN`_OW|C+t}hMqJ3;~xc0r| zNmDHq6>PrCo%YKPu=WKvP}2Wj{$?I(}Q_Z3ZWlYGrSOxR6QXEswbOD)GJNS=YKf4D{udR^as>TZ243 z+i!5$%4sC>evyy$880sz|KXP(>L|bH2PxKqDaxs8%C~L3SZY{ev}iwWKl^h30i@6I zr0>bM41+>K3f?G$?^C8uo153oZ1QT=WS7~MYM4rtiM{5mqoYHt8`T58>Srs*TcH%i zNWC7axTNF?H23l2$7hOue52w zkhhl?x1(c5E}#zfBgewgju$`Zs)gB<TJtN=8+;ijHTD8dLvGy{BKULK{#^2;sZ`yycEc`llF&VzpF zjGx~`K%l?7HpEiCu7vf@@tKAQG4L{|es5W=5aQb1-90Vc%Urbj`O>BGL^wPGmUZaL z#2bRR`Qt}cXSK6xrXG{A@9JWI$l2nbMvk6RdPNgu{ID`eUTJ9?DC_c z^$n1kXi>5;1TSF6kN>WG$Bb7JV|{I%O0R4UQv9V#5;ms6<6 z!*ACBwW~^v%;J!^Jl>QGVrfHfvG2u1_rX`V6ckmxBA3t4_vP3eD7gu$&vZE|H=4S! zZj-8{x2vl!$6s`nHZ5(Tt}ZpSy(f*_GE!AkgtS*}gUBD0+$Lsu!fgM2OAO>?rrHI_ zHYRg_e@@oeRa$FnE76L-x}t2I)m_81q^t(xa3Td(4cFw3-3OvBf3l=X+l;W}pv35O zF_?i2PZZu+u?nb({Bz4n<`P$ZE`o)#1VT5%%K&XgrX$0{fxfTgE!BB0 ziQX6*82A=7opm^_XUpcO>>77UH>r=Chn_lJ+ov((0xSj+H)%taN_)$yw>dS5LD_NB zPRO)0d_8KK{JGVE7}}WQn%FYdmwh5QgQ8IQ_OUC@h%#q|pSVgiUY!fP%aiWCEjschaL{N+V

Fr?3T$3fy!cI}~htNzN!si>Fl7!zN(@ZUNpHZ5JgTbVre)RTEuSbj)0PPK{TU&o=sUc6HjGh@j zVrwkFxDpJAa(fu)_}v$&tBO%QTcA@skoR@IGT>?v$G!snD$g%0 zY|@R5iRsIE_7&;=EZe`9#H~Gj42Q!-M3m7nG*G<*A0%t3o=Z460HQkx$ek^S0ES(n zAY)aaF~-+oQYuwS@=~9Y=bGA4>LE7b>~BtWKGp~07B3Tm`L$Q+bUM+FZYSMUH**ZN zs>oBnx-iWF3Riq{gPy&;J%jTMeSUeal-fn5130IX z&kujP)8v~Tz`=^aJFc`S)hJhC5yN0GTA*|g*ifIzdu{lgUl)%TmO7pcN?ef3dwY6- zQPu5lUgV__+`lf>%G&w`@DvpxMIMrY0ss6&DC*U*;YA96~a)I2gfM z9Kc7^&L+g!**Q2cHwb4(8wd)v6I=-V7&d<4j8Ic;uT)9eOLsx_XxepH-3&NrSY!VV zM@`n&{}*9w3_fM3j@8|@HAs?sZts}k@*R+?{cBWsj^M6YManTplE>d{h4|mDg=*~` Y)$b=B-1EBu?7&(lj(ND19u2(lPb9(kr~m)} From 1966deec00787fccf80bba838f84d1b0087e7ae6 Mon Sep 17 00:00:00 2001 From: Vaivaswat Date: Tue, 4 Nov 2025 00:09:54 +0530 Subject: [PATCH 37/37] trying to fix arc issue --- .../src/test/typography/TypographyTest.java | 205 ++++++++---------- 1 file changed, 95 insertions(+), 110 deletions(-) diff --git a/core/test/processing/visual/src/test/typography/TypographyTest.java b/core/test/processing/visual/src/test/typography/TypographyTest.java index 52f968639a..06be6fffe0 100644 --- a/core/test/processing/visual/src/test/typography/TypographyTest.java +++ b/core/test/processing/visual/src/test/typography/TypographyTest.java @@ -5,6 +5,10 @@ import processing.visual.src.test.base.VisualTest; import processing.visual.src.core.ProcessingSketch; import processing.visual.src.core.TestConfig; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import java.util.stream.Stream; @Tag("typography") @Tag("text") @@ -86,120 +90,101 @@ public void draw(PApplet p) { @DisplayName("textAlign Tests") class TextAlignTests { - @Test + @ParameterizedTest(name = "Alignment: {0}-{1}") + @MethodSource("alignmentProvider") @DisplayName("All horizontal and vertical alignments with single word") - public void testAllAlignmentsSingleWord() { - int[][] alignments = { - {PApplet.LEFT, PApplet.TOP}, - {PApplet.CENTER, PApplet.TOP}, - {PApplet.RIGHT, PApplet.TOP}, - {PApplet.LEFT, PApplet.CENTER}, - {PApplet.CENTER, PApplet.CENTER}, - {PApplet.RIGHT, PApplet.CENTER}, - {PApplet.LEFT, PApplet.BOTTOM}, - {PApplet.CENTER, PApplet.BOTTOM}, - {PApplet.RIGHT, PApplet.BOTTOM} - }; - - TestConfig config = new TestConfig(300, 300); - - for (int[] alignment : alignments) { - final int alignX = alignment[0]; - final int alignY = alignment[1]; - final String alignName = getAlignmentName(alignX, alignY); - - assertVisualMatch("typography/align/single-word-" + alignName, - new ProcessingSketch() { - PFont font; - - @Override - public void setup(PApplet p) { - font = p.createFont("SansSerif", 60); - p.textFont(font); - } - - @Override - public void draw(PApplet p) { - p.background(255); - p.textAlign(alignX, alignY); - p.fill(0); // ← Must be in draw() - p.text("Single Line", p.width / 2, p.height / 2); - - // Draw bounding box - p.noFill(); - p.stroke(255, 0, 0); - p.strokeWeight(2); - - float tw = p.textWidth("Single Line"); - float th = p.textAscent() + p.textDescent(); - float x = calculateX(p, alignX, p.width / 2f, tw); - float y = calculateY(p, alignY, p.height / 2f, th); - p.rect(x, y, tw, th); - } - }, config); - } + public void testAllAlignmentsSingleWord(int alignX, int alignY) { + final String alignName = getAlignmentName(alignX, alignY); + + assertVisualMatch("typography/align/single-word-" + alignName, + new ProcessingSketch() { + PFont font; + + @Override + public void setup(PApplet p) { + font = p.createFont("SansSerif", 60); + p.textFont(font); + } + + @Override + public void draw(PApplet p) { + p.background(255); + p.textAlign(alignX, alignY); + p.fill(0); + p.text("Single Line", p.width / 2, p.height / 2); + + // Draw bounding box + p.noFill(); + p.stroke(255, 0, 0); + p.strokeWeight(2); + + float tw = p.textWidth("Single Line"); + float th = p.textAscent() + p.textDescent(); + float x = calculateX(p, alignX, p.width / 2f, tw); + float y = calculateY(p, alignY, p.height / 2f, th); + p.rect(x, y, tw, th); + } + }, new TestConfig(600, 300)); } - @Test + @ParameterizedTest(name = "Multi-line alignment: {0}-{1}") + @MethodSource("alignmentProvider") @DisplayName("Multi-line text with manual line breaks") - public void testMultiLineManualText() { - int[][] alignments = { - {PApplet.LEFT, PApplet.TOP}, - {PApplet.CENTER, PApplet.TOP}, - {PApplet.RIGHT, PApplet.TOP}, - {PApplet.LEFT, PApplet.CENTER}, - {PApplet.CENTER, PApplet.CENTER}, - {PApplet.RIGHT, PApplet.CENTER}, - {PApplet.LEFT, PApplet.BOTTOM}, - {PApplet.CENTER, PApplet.BOTTOM}, - {PApplet.RIGHT, PApplet.BOTTOM} - }; - - TestConfig config = new TestConfig(150, 100); - - for (int[] alignment : alignments) { - final int alignX = alignment[0]; - final int alignY = alignment[1]; - final String alignName = getAlignmentName(alignX, alignY); - - assertVisualMatch("typography/align/multi-line-" + alignName, - new ProcessingSketch() { - PFont font; - - @Override - public void setup(PApplet p) { - font = p.createFont("SansSerif", 12); - p.textFont(font); - } - - @Override - public void draw(PApplet p) { - p.background(255); - - float xPos = 20; - float yPos = 20; - float boxWidth = 100; - float boxHeight = 60; - - // Draw box - p.noFill(); - p.stroke(200); - p.strokeWeight(2); - p.rect(xPos, yPos, boxWidth, boxHeight); - - // Draw text - p.fill(0); // ← Must be before text - p.noStroke(); - p.textAlign(alignX, alignY); - p.text("Line 1\nLine 2\nLine 3", xPos, yPos, boxWidth, boxHeight); - - // Draw bounding box (optional) - p.noFill(); - p.stroke(255, 0, 0); - p.strokeWeight(1); - } - }, config); - } + public void testMultiLineManualText(int alignX, int alignY) { + final String alignName = getAlignmentName(alignX, alignY); + + assertVisualMatch("typography/align/multi-line-" + alignName, + new ProcessingSketch() { + PFont font; + + @Override + public void setup(PApplet p) { + font = p.createFont("SansSerif", 12); + p.textFont(font); + } + + @Override + public void draw(PApplet p) { + p.background(255); + + float xPos = 20; + float yPos = 20; + float boxWidth = 100; + float boxHeight = 60; + + // Draw box + p.noFill(); + p.stroke(200); + p.strokeWeight(2); + p.rect(xPos, yPos, boxWidth, boxHeight); + + // Draw text + p.fill(0); + p.noStroke(); + p.textAlign(alignX, alignY); + p.text("Line 1\nLine 2\nLine 3", xPos, yPos, boxWidth, boxHeight); + + // Draw bounding box + p.noFill(); + p.stroke(255, 0, 0); + p.strokeWeight(1); + } + }, new TestConfig(150, 100)); + } + + // Provide alignment combinations + static Stream alignmentProvider() { + return Stream.of( + Arguments.of(PApplet.LEFT, PApplet.TOP), + Arguments.of(PApplet.CENTER, PApplet.TOP), + Arguments.of(PApplet.RIGHT, PApplet.TOP), + Arguments.of(PApplet.LEFT, PApplet.CENTER), + Arguments.of(PApplet.CENTER, PApplet.CENTER), + Arguments.of(PApplet.RIGHT, PApplet.CENTER), + Arguments.of(PApplet.LEFT, PApplet.BOTTOM), + Arguments.of(PApplet.CENTER, PApplet.BOTTOM), + Arguments.of(PApplet.RIGHT, PApplet.BOTTOM) + ); } // Helper methods