oxedyne/fe2o3/fe2o3_graphics/tests/png/gen.sh
7.9 KiB, 1 run, executable
created by r1870400018:18098, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | #!/bin/bash |
| 2 | # Regenerates the depth, colour-type and interlace fixtures in this directory. |
| 3 | # |
| 4 | # Nothing here is a photograph, and nothing here was written by the codec under test: a short Python |
| 5 | # program writes each source image as a PPM or a PAM, ImageMagick turns that into a PNG of the |
| 6 | # requested bit depth, colour type and interlace method, and ImageMagick reads its own PNG back out |
| 7 | # as the PAM the test compares against. A codec that agrees only with itself has been tested against |
| 8 | # nothing. |
| 9 | # |
| 10 | # ImageMagick will quietly ignore `png:bit-depth` and `png:color-type` when the image it holds does |
| 11 | # not suit them, so the recipes below shape the image first -- quantising, greying, forcing a |
| 12 | # channel depth -- and the test re-reads each fixture's IHDR to check that what came out is what the |
| 13 | # fixture's name claims. A silently downgraded fixture is a hole in the matrix, not a passing test. |
| 14 | # |
| 15 | # The `tRNS` fixtures come out of ImageMagick's `PNG8:` writer, which is the only route it offers to |
| 16 | # a palette image with transparency at a sub-byte depth, and which needs a source holding few enough |
| 17 | # colours that the palette fits the depth. It will not write greyscale transparency at a sub-byte |
| 18 | # depth at all, so that combination is covered by a hand-built unit test in `png.rs` instead. |
| 19 | # |
| 20 | # The files `grey_trns.png`, `palette_trns.png`, `rgb_plain.png`, `rgb_trns.png` and |
| 21 | # `rgba_plain.png` are older, come from Pillow by way of `gen.py`, and are not touched here. |
| 22 | # |
| 23 | # Requires ImageMagick's `convert` and Python 3. Run from this directory. |
| 24 | set -eu |
| 25 | cd "$(dirname "$0")" |
| 26 | tmp=$(mktemp -d) |
| 27 | trap 'rm -rf "$tmp"' EXIT |
| 28 | |
| 29 | # The sizes: one at which every Adam7 pass is non-empty and partial, and a handful small enough that |
| 30 | # most passes hold nothing at all. |
| 31 | FULL=17x13 |
| 32 | SMALL="1x1 3x2 5x1 1x5 9x9" |
| 33 | |
| 34 | # ---- The sources, several per size: 8- and 16-bit, with and without alpha, and four palettes. ---- |
| 35 | python3 - "$tmp" $FULL $SMALL <<'PYEOF' |
| 36 | import os |
| 37 | import sys |
| 38 | |
| 39 | d = sys.argv[1] |
| 40 | |
| 41 | def pix(x, y): |
| 42 | """A deterministic colour and alpha, chosen to leave no two channels equal.""" |
| 43 | r = (x * 47 + y * 11 + 3) % 256 |
| 44 | g = (x * 13 + y * 61 + 71) % 256 |
| 45 | b = (x * 29 + y * 89 + 151) % 256 |
| 46 | a = (x * 37 + y * 101 + 19) % 256 |
| 47 | return r, g, b, a |
| 48 | |
| 49 | # The colours the palette sources draw on, in the order they are taken. |
| 50 | PAL = [ |
| 51 | (255, 80, 20), (0, 255, 0), (0, 0, 255), (255, 255, 0), (0, 255, 255), (255, 0, 255), |
| 52 | (10, 20, 30), (200, 100, 50), (60, 180, 90), (90, 30, 220), (240, 240, 10), (5, 120, 200), |
| 53 | ] |
| 54 | |
| 55 | def write(name, header, px): |
| 56 | open(os.path.join(d, name), "wb").write(header + bytes(px)) |
| 57 | |
| 58 | for spec in sys.argv[2:]: |
| 59 | w, h = (int(v) for v in spec.split("x")) |
| 60 | p6_8 = b"P6\n%d %d\n255\n" % (w, h) |
| 61 | p6_16 = b"P6\n%d %d\n65535\n" % (w, h) |
| 62 | p7_8 = b"P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 255\nTUPLTYPE RGB_ALPHA\nENDHDR\n" % (w, h) |
| 63 | p7_16 = b"P7\nWIDTH %d\nHEIGHT %d\nDEPTH 4\nMAXVAL 65535\nTUPLTYPE RGB_ALPHA\nENDHDR\n" % (w, h) |
| 64 | |
| 65 | # Eight bits, no alpha. |
| 66 | px = bytearray() |
| 67 | for y in range(h): |
| 68 | for x in range(w): |
| 69 | px += bytes(pix(x, y)[:3]) |
| 70 | write("rgb8_%s.ppm" % spec, p6_8, px) |
| 71 | |
| 72 | # Sixteen bits, no alpha. The samples are not multiples of 257, so the low byte carries |
| 73 | # something a reduction to eight bits has to decide what to do with. |
| 74 | px = bytearray() |
| 75 | for y in range(h): |
| 76 | for x in range(w): |
| 77 | for i, c in enumerate(pix(x, y)[:3]): |
| 78 | v = (c * 257 + x * 7 + y * 13 + i * 3) % 65536 |
| 79 | px += bytes((v >> 8, v & 255)) |
| 80 | write("rgb16_%s.ppm" % spec, p6_16, px) |
| 81 | |
| 82 | # Eight bits with alpha. |
| 83 | px = bytearray() |
| 84 | for y in range(h): |
| 85 | for x in range(w): |
| 86 | px += bytes(pix(x, y)) |
| 87 | write("rgba8_%s.pam" % spec, p7_8, px) |
| 88 | |
| 89 | # Sixteen bits with alpha. |
| 90 | px = bytearray() |
| 91 | for y in range(h): |
| 92 | for x in range(w): |
| 93 | for i, c in enumerate(pix(x, y)): |
| 94 | v = (c * 257 + x * 7 + y * 13 + i * 3) % 65536 |
| 95 | px += bytes((v >> 8, v & 255)) |
| 96 | write("rgba16_%s.pam" % spec, p7_16, px) |
| 97 | |
| 98 | # Two colours far enough apart that a quantisation to two keeps both. The general source |
| 99 | # above is smooth enough that ImageMagick maps all of it onto one entry, which would make a |
| 100 | # 1-bit palette fixture a picture of nothing. |
| 101 | px = bytearray() |
| 102 | for y in range(h): |
| 103 | for x in range(w): |
| 104 | px += bytes((250, 40, 10) if (x + y) % 2 == 0 else (5, 30, 240)) |
| 105 | write("duo_%s.ppm" % spec, p6_8, px) |
| 106 | |
| 107 | # A few saturated colours, one pixel in five fully transparent: the shape ImageMagick's PNG8 |
| 108 | # writer turns into a palette with a tRNS chunk. One source per palette size, because the |
| 109 | # palette has to fit the bit depth asked for. |
| 110 | for n in (1, 3, 6, 12): |
| 111 | px = bytearray() |
| 112 | for y in range(h): |
| 113 | for x in range(w): |
| 114 | c = PAL[(x + 2 * y) % n] |
| 115 | a = 0 if (x + 3 * y) % 5 == 0 else 255 |
| 116 | px += bytes((c[0], c[1], c[2], a)) |
| 117 | write("pal%d_%s.pam" % (n, spec), p7_8, px) |
| 118 | PYEOF |
| 119 | |
| 120 | # ---- The fixtures. ---- |
| 121 | # $1 is the combination's name, $2 the size, $3 `n` or `i` for the interlace method, and the rest |
| 122 | # the recipe, with a leading `@` on the source's name standing for the temporary directory. |
| 123 | make() { |
| 124 | local name=$1 size=$2 lace=$3 |
| 125 | shift 3 |
| 126 | local il=none |
| 127 | [ "$lace" = i ] && il=PNG |
| 128 | local args=() |
| 129 | for a in "$@"; do |
| 130 | args+=("${a/@/$tmp/}") |
| 131 | done |
| 132 | convert "${args[0]}" +dither -strip -interlace "$il" "${args[@]:1}" |
| 133 | mv out.png "${name}_${size}_${lace}.png" |
| 134 | convert "${name}_${size}_${lace}.png" -alpha set -depth 8 "pam:${name}_${size}_${lace}.pam" |
| 135 | } |
| 136 | |
| 137 | # One combination at one size, in both interlace methods. |
| 138 | both() { |
| 139 | local name=$1 size=$2 |
| 140 | shift 2 |
| 141 | make "$name" "$size" n "$@" |
| 142 | make "$name" "$size" i "$@" |
| 143 | } |
| 144 | |
| 145 | # Every legal depth and colour type, at a size where all seven Adam7 passes hold pixels. |
| 146 | s=$FULL |
| 147 | both g1 "$s" "@rgb8_$s.ppm" -colorspace Gray -type Bilevel png:out.png |
| 148 | both g2 "$s" "@rgb8_$s.ppm" -colorspace Gray -type Grayscale -depth 2 \ |
| 149 | -define png:color-type=0 -define png:bit-depth=2 png:out.png |
| 150 | both g4 "$s" "@rgb8_$s.ppm" -colorspace Gray -type Grayscale -depth 4 \ |
| 151 | -define png:color-type=0 -define png:bit-depth=4 png:out.png |
| 152 | both g8 "$s" "@rgb8_$s.ppm" -colorspace Gray -type Grayscale -depth 8 \ |
| 153 | -define png:color-type=0 -define png:bit-depth=8 png:out.png |
| 154 | both g16 "$s" "@rgb16_$s.ppm" -alpha off -colorspace Gray -type Grayscale -depth 16 \ |
| 155 | -define png:color-type=0 -define png:bit-depth=16 png:out.png |
| 156 | both p1 "$s" "@duo_$s.ppm" -define png:bit-depth=1 PNG8:out.png |
| 157 | both p2 "$s" "@rgb8_$s.ppm" -type Palette -colors 3 -depth 2 \ |
| 158 | -define png:color-type=3 -define png:bit-depth=2 png:out.png |
| 159 | both p4 "$s" "@rgb8_$s.ppm" -type Palette -colors 12 -depth 4 \ |
| 160 | -define png:color-type=3 -define png:bit-depth=4 png:out.png |
| 161 | both p8 "$s" "@rgb8_$s.ppm" -type Palette -colors 32 -depth 8 \ |
| 162 | -define png:color-type=3 -define png:bit-depth=8 png:out.png |
| 163 | both pt1 "$s" "@pal1_$s.pam" -define png:bit-depth=1 PNG8:out.png |
| 164 | both pt2 "$s" "@pal3_$s.pam" -define png:bit-depth=2 PNG8:out.png |
| 165 | both pt4 "$s" "@pal6_$s.pam" -define png:bit-depth=4 PNG8:out.png |
| 166 | both pt8 "$s" "@pal12_$s.pam" -define png:bit-depth=8 PNG8:out.png |
| 167 | both rgb8 "$s" "@rgb8_$s.ppm" PNG24:out.png |
| 168 | both rgb16 "$s" "@rgb16_$s.ppm" -alpha off -depth 16 \ |
| 169 | -define png:color-type=2 -define png:bit-depth=16 png:out.png |
| 170 | both ga8 "$s" "@rgba8_$s.pam" -colorspace Gray -type GrayscaleAlpha -depth 8 \ |
| 171 | -define png:color-type=4 -define png:bit-depth=8 png:out.png |
| 172 | both ga16 "$s" "@rgba16_$s.pam" -colorspace Gray -type GrayscaleAlpha -depth 16 \ |
| 173 | -define png:color-type=4 -define png:bit-depth=16 png:out.png |
| 174 | both rgba8 "$s" "@rgba8_$s.pam" PNG32:out.png |
| 175 | both rgba16 "$s" "@rgba16_$s.pam" -depth 16 \ |
| 176 | -define png:color-type=6 -define png:bit-depth=16 png:out.png |
| 177 | |
| 178 | # The sizes at which Adam7 passes fall empty, in the narrowest and the widest pixel the format has: |
| 179 | # one bit of greyscale, and four channels of eight and of sixteen bits. |
| 180 | for s in $SMALL; do |
| 181 | both g1 "$s" "@rgb8_$s.ppm" -colorspace Gray -type Bilevel png:out.png |
| 182 | both rgba8 "$s" "@rgba8_$s.pam" PNG32:out.png |
| 183 | both rgba16 "$s" "@rgba16_$s.pam" -depth 16 \ |
| 184 | -define png:color-type=6 -define png:bit-depth=16 png:out.png |
| 185 | done |
| 186 | |
| 187 | ls -l |