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 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
|
"""Test the final, drawn results and compare PNG images pixel per pixel."""
import io
from itertools import zip_longest
from pathlib import Path
from PIL import Image
from ..testing_utils import FakeHTML, resource_path
# NOTE: "r" is not half red on purpose. In the pixel strings it has
# better contrast with "B" than does "R". eg. "rBBBrrBrB" vs "RBBBRRBRB".
PIXELS_BY_CHAR = {
'_': (255, 255, 255), # white
'R': (255, 0, 0), # red
'B': (0, 0, 255), # blue
'G': (0, 255, 0), # lime green
'V': (191, 0, 64), # average of 1*B and 3*R
'S': (255, 63, 63), # R above R above _
'C': (0, 255, 255), # cyan
'M': (255, 0, 255), # magenta
'Y': (255, 255, 0), # yellow
'K': (0, 0, 0), # black
'r': (255, 0, 0), # red
'g': (0, 128, 0), # half green
'b': (0, 0, 128), # half blue
'v': (128, 0, 128), # average of B and R
's': (255, 127, 127), # R above _
't': (127, 255, 127), # G above _
'u': (128, 0, 127), # r above B above _
'h': (64, 0, 64), # half average of B and R
'a': (0, 0, 254), # R in lossy JPG
'p': (192, 0, 63), # R above R above B above _
'z': None,
}
def parse_pixels(pixels):
lines = (line.split('#')[0].strip() for line in pixels.splitlines())
lines = tuple(line for line in lines if line)
widths = {len(line) for line in lines}
assert len(widths) == 1, 'All lines of pixels must have the same width'
width = widths.pop()
height = len(lines)
pixels = tuple(PIXELS_BY_CHAR[char] for line in lines for char in line)
return width, height, pixels
def assert_pixels(name, expected_pixels, html):
"""Helper testing the size of the image and the pixels values."""
expected_width, expected_height, expected_pixels = parse_pixels(
expected_pixels)
width, height, pixels = html_to_pixels(html)
assert (expected_width, expected_height) == (width, height), (
'Images do not have the same sizes:\n'
f'- expected: {expected_width} × {expected_height}\n'
f'- result: {width} × {height}')
assert_pixels_equal(name, width, height, pixels, expected_pixels)
def assert_same_renderings(name, *documents, tolerance=0):
"""Render HTML documents to PNG and check that they're the same."""
pixels_list = []
for html in documents:
width, height, pixels = html_to_pixels(html)
pixels_list.append(pixels)
reference = pixels_list[0]
for i, pixels in enumerate(pixels_list[1:], start=1):
assert_pixels_equal(
f'{name}_{i}', width, height, pixels, reference, tolerance)
def assert_different_renderings(name, *documents):
"""Render HTML documents to PNG and check that they’re different."""
pixels_list = []
for html in documents:
width, height, pixels = html_to_pixels(html)
pixels_list.append(pixels)
for i, pixels_1 in enumerate(pixels_list, start=1):
for j, pixels_2 in enumerate(pixels_list[i:], start=i+1):
if pixels_1 == pixels_2: # pragma: no cover
name_1, name_2 = f'{name}_{i}', f'{name}_{j}'
write_png(name_1, pixels_1, width, height)
assert False, f'{name_1} and {name_2} are the same'
def assert_pixels_equal(name, width, height, raw, expected_raw, tolerance=0):
"""Take 2 matrices of pixels and assert that they are the same."""
if raw != expected_raw: # pragma: no cover
pixels = zip_longest(raw, expected_raw, fillvalue=(-1, -1, -1))
for i, (value, expected) in enumerate(pixels):
if expected is None:
continue
if any(abs(value - expected) > tolerance
for value, expected in zip(value, expected)):
actual_height = len(raw) // width
write_png(name, raw, width, actual_height)
expected_raw = [
pixel or (255, 255, 255) for pixel in expected_raw]
write_png(f'{name}.expected', expected_raw, width, height)
x = i % width
y = i // width
assert 0, (
f'Pixel ({x}, {y}) in {name}: '
f'expected rgba{expected}, got rgba{value}')
def write_png(name, pixels, width, height): # pragma: no cover
"""Take a pixel matrix and write a PNG file."""
directory = Path(__file__).parent / 'results'
directory.mkdir(exist_ok=True)
image = Image.new('RGB', (width, height))
image.putdata(pixels)
image.save(directory / f'{name}.png')
def html_to_pixels(html):
"""Render an HTML document to PNG, checks its size and return pixel data.
Also return the document to aid debugging.
"""
document = FakeHTML(string=html, base_url=resource_path('<dummy>'))
return document_to_pixels(document)
def document_to_pixels(document):
"""Render an HTML document to PNG, check its size and return pixel data."""
image = Image.open(io.BytesIO(document.write_png()))
return image.width, image.height, image.getdata()
|