""" weasyprint.tests.test_draw -------------------------- Test the final, drawn results and compare PNG images pixel per pixel. :copyright: Copyright 2011-2019 Simon Sapin and contributors, see AUTHORS. :license: BSD, see LICENSE for details. """ import os import sys import cairocffi as cairo from ..testing_utils import FakeHTML, resource_filename # RGBA to native-endian ARGB as_pixel = ( lambda x: x[:-1][::-1] + x[-1:] if sys.byteorder == 'little' else lambda x: x[-1:] + x[:-1]) PIXELS_BY_CHAR = dict( _=as_pixel(b'\xff\xff\xff\xff'), # white R=as_pixel(b'\xff\x00\x00\xff'), # red B=as_pixel(b'\x00\x00\xff\xff'), # blue G=as_pixel(b'\x00\xff\x00\xff'), # lime green V=as_pixel(b'\xBF\x00\x40\xff'), # average of 1*B and 3*R. S=as_pixel(b'\xff\x3f\x3f\xff'), # R above R above #fff r=as_pixel(b'\xff\x00\x00\xff'), # red g=as_pixel(b'\x00\x80\x00\xff'), # half green b=as_pixel(b'\x00\x00\x80\xff'), # half blue v=as_pixel(b'\x80\x00\x80\xff'), # average of B and R. h=as_pixel(b'\x40\x00\x40\xff'), # half average of B and R. a=as_pixel(b'\x00\x00\xfe\xff'), # JPG is lossy... p=as_pixel(b'\xc0\x00\x3f\xff'), # R above R above B above #fff. ) # 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". def parse_pixels(pixels, pixels_overrides=None): chars = dict(PIXELS_BY_CHAR, **(pixels_overrides or {})) lines = [line.split('#')[0].strip() for line in pixels.splitlines()] return [b''.join(chars[char] for char in line) for line in lines if line] def assert_pixels(name, expected_width, expected_height, expected_pixels, html): """Helper testing the size of the image and the pixels values.""" if isinstance(expected_pixels, str): expected_pixels = parse_pixels(expected_pixels) assert len(expected_pixels) == expected_height assert len(expected_pixels[0]) == expected_width * 4 expected_raw = b''.join(expected_pixels) _doc, pixels = html_to_pixels(name, expected_width, expected_height, html) assert_pixels_equal( name, expected_width, expected_height, pixels, expected_raw) def assert_same_rendering(expected_width, expected_height, documents, tolerance=0): """Render HTML documents to PNG and check that they render the same. Each document is passed as a (name, html_source) tuple. """ pixels_list = [] for name, html in documents: _doc, pixels = html_to_pixels( name, expected_width, expected_height, html) pixels_list.append((name, pixels)) _name, reference = pixels_list[0] for name, pixels in pixels_list[1:]: assert_pixels_equal(name, expected_width, expected_height, reference, pixels, tolerance) def assert_different_renderings(expected_width, expected_height, documents): """Render HTML documents to PNG and check that they don't render the same. Each document is passed as a (name, html_source) tuple. """ pixels_list = [] for name, html in documents: _doc, pixels = html_to_pixels( name, expected_width, expected_height, html) pixels_list.append((name, pixels)) for i, (name_1, pixels_1) in enumerate(pixels_list): for name_2, pixels_2 in pixels_list[i + 1:]: if pixels_1 == pixels_2: # pragma: no cover write_png(name_1, pixels_1, expected_width, expected_height) # Same as "assert pixels_1 != pixels_2" but the output of # the assert hook would be gigantic and useless. assert False, '%s and %s are the same' % (name_1, name_2) def write_png(basename, pixels, width, height): # pragma: no cover """Take a pixel matrix and write a PNG file.""" directory = os.path.join(os.path.dirname(__file__), 'results') if not os.path.isdir(directory): os.mkdir(directory) filename = os.path.join(directory, basename + '.png') cairo.ImageSurface( cairo.FORMAT_ARGB32, width, height, data=bytearray(pixels), stride=width * 4 ).write_to_png(filename) def html_to_pixels(name, expected_width, expected_height, 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, # Dummy filename, but in the right directory. base_url=resource_filename('')) pixels = document_to_pixels( document, name, expected_width, expected_height) return document, pixels def document_to_pixels(document, name, expected_width, expected_height): """Render an HTML document to PNG, check its size and return pixel data.""" surface = document.write_image_surface() return image_to_pixels(surface, expected_width, expected_height) def image_to_pixels(surface, width, height): assert (surface.get_width(), surface.get_height()) == (width, height) # RGB24 is actually the same as ARGB32, with A unused. assert surface.get_format() in (cairo.FORMAT_ARGB32, cairo.FORMAT_RGB24) pixels = surface.get_data()[:] assert len(pixels) == width * height * 4 return pixels 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 for i, (value, expected) in enumerate(zip(raw, expected_raw)): if abs(value - expected) > tolerance: write_png(name, raw, width, height) write_png(name + '.expected', expected_raw, width, height) pixel_n = i // 4 x = pixel_n // width y = pixel_n % width i % 4 pixel = tuple(list(raw[i:i + 4])) expected_pixel = tuple(list( expected_raw[i:i + 4])) assert 0, ( 'Pixel (%i, %i) in %s: expected rgba%s, got rgba%s' % (x, y, name, expected_pixel, pixel))