aboutsummaryrefslogtreecommitdiff
path: root/test-rig/Pixhash.java
blob: 3bd8c8a925941c5f88cbec26189f0355b638c570 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Verify a screencap. The default checks non-uniformity. --pattern also
// requires the four large color fields drawn by the debug Pattern activity,
// so ordinary app chrome cannot make a black video surface pass.
//
//   java test-rig/Pixhash.java <png>            # default min=5%
//   java test-rig/Pixhash.java <png> --pattern
//
// ImageIO/BufferedImage live in java.desktop, which the image's
// openjdk-17-jdk-headless package does ship (headless only disables
// display/input, not imaging) - no extra dependency needed.

import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;

public final class Pixhash {

    public static void main(String[] args) throws IOException {
        if (args.length < 1) usage();
        double minPercent = 5.0;
        boolean pattern = false;
        for (int i = 1; i < args.length; i++) {
            if ("--min".equals(args[i])) {
                if (++i >= args.length) usage();
                try {
                    minPercent = Double.parseDouble(args[i]);
                } catch (NumberFormatException e) {
                    usage();
                }
            } else if ("--pattern".equals(args[i])) {
                pattern = true;
            } else {
                usage();
            }
        }
        if (!Double.isFinite(minPercent) || minPercent < 0.0 || minPercent > 100.0) {
            usage();
        }

        BufferedImage img = ImageIO.read(new File(args[0]));
        if (img == null) {
            System.err.println("pixhash: could not read " + args[0]);
            System.exit(2);
        }

        int w = img.getWidth(), h = img.getHeight();
        int ref = img.getRGB(0, 0);
        long total = (long) w * h;
        long diff = 0;
        for (int y = 0; y < h; y++) {
            for (int x = 0; x < w; x++) {
                if (img.getRGB(x, y) != ref) diff++;
            }
        }
        double pct = 100.0 * diff / total;
        System.out.printf("pixhash: %d/%d differ from (0,0)=%08x  =>  %.2f%%%n",
                diff, total, ref, pct);
        if (pct < minPercent) {
            System.err.printf("pixhash: only %.2f%% differ, need %.2f%%%n", pct, minPercent);
            System.exit(1);
        }
        if (pattern) checkPattern(img);
    }

    private static void usage() {
        System.err.println("usage: java Pixhash.java <png> [--min N] [--pattern]");
        System.exit(2);
    }

    private static void checkPattern(BufferedImage img) {
        long red = 0, green = 0, blue = 0, yellow = 0, sampled = 0;
        // Sampling every fourth pixel keeps the check cheap on 1080p images.
        for (int y = 0; y < img.getHeight(); y += 4) {
            for (int x = 0; x < img.getWidth(); x += 4) {
                int rgb = img.getRGB(x, y);
                int r = (rgb >>> 16) & 0xff;
                int g = (rgb >>> 8) & 0xff;
                int b = rgb & 0xff;
                sampled++;
                if (r >= 140 && g <= 110 && b <= 110) red++;
                else if (g >= 110 && r <= 120 && b <= 120) green++;
                else if (b >= 140 && r <= 110 && g <= 130) blue++;
                else if (r >= 140 && g >= 120 && b <= 120) yellow++;
            }
        }
        double rp = 100.0 * red / sampled;
        double gp = 100.0 * green / sampled;
        double bp = 100.0 * blue / sampled;
        double yp = 100.0 * yellow / sampled;
        System.out.printf("pixhash: pattern red=%.2f green=%.2f blue=%.2f yellow=%.2f%%%n",
                rp, gp, bp, yp);
        // A rotated 16:9 target fills only about one third of a portrait
        // source after correct letterboxing. Require every field and strong
        // combined coverage rather than assuming the video fills the screen.
        if (rp < 5.0 || gp < 5.0 || bp < 5.0 || yp < 5.0
                || rp + gp + bp + yp < 25.0) {
            System.err.println("pixhash: expected four-color target pattern was not rendered");
            System.exit(1);
        }
    }
}