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 000000000..124d3c8db Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/arc-center-linux.png differ 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 000000000..124d3c8db Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/arc-corner-linux.png differ 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 000000000..124d3c8db Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/arc-corners-linux.png differ 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 000000000..a97455efb Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/arc-negative-dimensions-linux.png differ 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 000000000..124d3c8db Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/arc-radius-linux.png differ 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 000000000..e0fd115f3 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-center-linux.png differ 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 000000000..e0fd115f3 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-corner-linux.png differ 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 000000000..e0fd115f3 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-corners-linux.png differ 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 000000000..fe97057fa Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-negative-dimensions-linux.png differ 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 000000000..e0fd115f3 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/ellipse-radius-linux.png differ 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 000000000..f8b37838e Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/rect-center-linux.png differ 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 000000000..f8b37838e Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/rect-corner-linux.png differ 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 000000000..f8b37838e Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/rect-corners-linux.png differ 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 000000000..18a0a4a46 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/rect-negative-dimensions-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/shape-modes/rect-radius-linux.png b/core/test/processing/visual/__screenshots__/shape-modes/rect-radius-linux.png new file mode 100644 index 000000000..f8b37838e Binary files /dev/null and b/core/test/processing/visual/__screenshots__/shape-modes/rect-radius-linux.png differ 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 000000000..d7d33e10a Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-bottom-linux.png differ 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 000000000..91f425290 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-center-linux.png differ 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 000000000..e8455adf5 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-center-top-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-bottom-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-bottom-linux.png new file mode 100644 index 000000000..02d38070b Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-bottom-linux.png differ 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 000000000..cfc6d8891 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-center-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-top-linux.png b/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-top-linux.png new file mode 100644 index 000000000..40d07d7ca Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-left-top-linux.png differ 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 000000000..f55c6d27c Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-bottom-linux.png differ 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 000000000..8eefc1b37 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-center-linux.png differ 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 000000000..d600d172b Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/multi-line-right-top-linux.png differ 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 new file mode 100644 index 000000000..11937e157 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-center-bottom-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-center-center-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-center-center-linux.png new file mode 100644 index 000000000..6171360ef Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-center-center-linux.png differ 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 000000000..c3f7f02b4 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-center-top-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-left-bottom-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-left-bottom-linux.png new file mode 100644 index 000000000..a9c2ba4cd Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-left-bottom-linux.png differ 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 000000000..f736f6c20 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-left-center-linux.png differ 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 new file mode 100644 index 000000000..008db894f Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-left-top-linux.png differ 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 000000000..77db6984e Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-bottom-linux.png differ 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 000000000..a1b1fb011 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-center-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/align/single-word-right-top-linux.png b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-top-linux.png new file mode 100644 index 000000000..6ae69540b Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/align/single-word-right-top-linux.png differ 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 000000000..df8be566f Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/complex/colored-text-linux.png differ 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 000000000..b90e1aaca Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/complex/rotated-text-linux.png differ 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 000000000..a9118ac7c Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/complex/transparent-text-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/font/default-font-linux.png b/core/test/processing/visual/__screenshots__/typography/font/default-font-linux.png new file mode 100644 index 000000000..fab815d50 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/font/default-font-linux.png differ 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 000000000..2964d7d80 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/font/monospace-font-linux.png differ 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 000000000..82e2a8270 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/font/system-font-linux.png differ 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 000000000..e7260fcc7 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/leading/different-values-linux.png differ 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 000000000..85dab54b3 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/pfont/ascent-descent-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/pfont/char-availability-linux.png b/core/test/processing/visual/__screenshots__/typography/pfont/char-availability-linux.png new file mode 100644 index 000000000..bc1f5ebf1 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/pfont/char-availability-linux.png differ 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 000000000..177befc93 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/size/sizes-comparison-linux.png differ diff --git a/core/test/processing/visual/__screenshots__/typography/width/string-width-linux.png b/core/test/processing/visual/__screenshots__/typography/width/string-width-linux.png new file mode 100644 index 000000000..d44f86015 Binary files /dev/null and b/core/test/processing/visual/__screenshots__/typography/width/string-width-linux.png differ 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 000000000..2b368e8e4 --- /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/test/shapemodes/ShapeModeTest.java b/core/test/processing/visual/src/test/shapemodes/ShapeModeTest.java new file mode 100644 index 000000000..b2c8c7efa --- /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 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 000000000..06be6fffe --- /dev/null +++ b/core/test/processing/visual/src/test/typography/TypographyTest.java @@ -0,0 +1,484 @@ +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; +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") +@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 { + + @ParameterizedTest(name = "Alignment: {0}-{1}") + @MethodSource("alignmentProvider") + @DisplayName("All horizontal and vertical alignments with single word") + 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)); + } + + @ParameterizedTest(name = "Multi-line alignment: {0}-{1}") + @MethodSource("alignmentProvider") + @DisplayName("Multi-line text with manual line breaks") + 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 + 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)); + } + } + + @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 diff --git a/settings.gradle.kts b/settings.gradle.kts index 285a19039..514ca2562 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -13,7 +13,4 @@ include( "java:libraries:serial", "java:libraries:svg" ) - include("app:utils") -include(":visual-tests") -