"""Compare unmodified QEMU framebuffer screenshots with website browser references.
This postprocessor does not create a native screenshot, run a browser, replace
fonts, resize away differences or treat a host image as guest acceptance.
"""
import argparse
import hashlib
import json
import math
from pathlib import Path
from PIL import Image, ImageChops, ImageDraw, ImageStat
def digest(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def read(path):
return json.loads(Path(path).read_text('utf-8-sig'))
def find_screenshots(value):
if isinstance(value, dict):
if isinstance(value.get('Path'), str) and isinstance(value.get('Sha256'), str):
yield value
for child in value.values():
yield from find_screenshots(child)
elif isinstance(value, list):
for child in value:
yield from find_screenshots(child)
def verify_native(path, receipt_path, allow_failed=False):
receipt = read(receipt_path)
assert receipt.get('Kind') == 'BareMetalNativeAotBootProof', 'Reference is not a native QEMU boot receipt'
assert receipt.get('Runtime', '').startswith('Cosmos NativeAOT'), 'Native runtime provenance missing'
assert receipt.get('QemuArgs') and '-qmp' in receipt['QemuArgs'], 'QEMU invocation is absent'
assert receipt.get('KernelSha256') and receipt.get('SourceSnapshotSha256') and receipt.get('ImageSha256'), 'Compiled image/source binding is absent'
snapshot = Path(receipt['SourceSnapshotPath'])
assert snapshot.exists() and digest(snapshot) == receipt['SourceSnapshotSha256'], 'Native compiled-source snapshot is missing or changed'
disk = Path(receipt['ImagePath'])
assert disk.exists() and digest(disk) == receipt['ImageSha256'], 'Native disk image differs from the accepted image hash'
if not allow_failed:
assert receipt.get('Passed') is True and receipt.get('ImageUnchanged') is True and receipt.get('QemuExitCode') == 0, 'Native acceptance failed; use --AllowFailedNative only for an explicitly failed diagnostic comparison'
image_hash = digest(path)
matching = [entry for entry in find_screenshots(receipt) if Path(entry['Path']).resolve() == path.resolve() and entry['Sha256'] == image_hash]
assert matching, 'Native screenshot path/hash does not appear in the QEMU receipt'
with Image.open(path) as image:
assert image.size == (receipt['NativeWidth'], receipt['NativeHeight']), 'Native image was resized or cropped before comparison'
return {'ReceiptPath': str(receipt_path.resolve()), 'ReceiptSha256': digest(receipt_path),
'ScreenshotPath': str(path.resolve()), 'ScreenshotSha256': image_hash,
'AcceptancePassed': receipt.get('Passed') is True,
'KernelSha256': receipt['KernelSha256'], 'ImageSha256': receipt['ImageSha256'],
'SourceSnapshotSha256': receipt['SourceSnapshotSha256'],
'NativeWidth': receipt['NativeWidth'], 'NativeHeight': receipt['NativeHeight'],
'MemoryMiB': receipt.get('MemoryMiB'), 'ScalePercent': receipt.get('ScalePercent'),
'SourceKind': 'Actual QEMU native framebuffer screendump'}
def verify_reference(path, receipt_path):
receipt = read(receipt_path)
assert receipt.get('Kind') == 'WebsiteBrowserReferenceScreenshot', 'Reference screenshot requires explicit website-browser provenance'
assert Path(receipt['ScreenshotPath']).resolve() == path.resolve(), 'Reference screenshot path differs from its receipt'
assert receipt['ScreenshotSha256'] == digest(path), 'Reference screenshot hash differs from its receipt'
assert len(receipt['WebsiteSourceManifestSha256']) == 64, 'Website source asset binding is absent'
viewport = receipt['ViewportCss']
assert viewport['Width'] > 0 and viewport['Height'] > 0 and 0 < viewport['DeviceScaleFactor'] <= 8
with Image.open(path) as image:
expected = (round(viewport['Width'] * viewport['DeviceScaleFactor']), round(viewport['Height'] * viewport['DeviceScaleFactor']))
assert image.size == expected, ('Reference pixels differ from the declared viewport/device scale', image.size, expected)
return {'ReceiptPath': str(receipt_path.resolve()), 'ReceiptSha256': digest(receipt_path),
'ScreenshotPath': str(path.resolve()), 'ScreenshotSha256': digest(path),
'WebsiteSourceManifestSha256': receipt['WebsiteSourceManifestSha256'], 'ViewportCss': viewport,
'SourceKind': 'Host website browser reference; never native guest acceptance',
'NativeGuestTested': False}
def roi(image, bounds):
x, y, width, height = bounds
assert min(x, y) >= 0 and min(width, height) > 0
assert x + width <= image.width and y + height <= image.height, ('ROI exceeds original screenshot bounds', bounds, image.size)
return image.crop((x, y, x + width, y + height)).convert('RGB')
def compare(native_path, native_receipt, reference_path, reference_receipt, native_roi, reference_roi, native_scale, reference_scale, output, allow_failed=False):
assert not output.exists(), 'Refusing to overwrite an existing comparison directory'
assert 0 < native_scale <= 8 and 0 < reference_scale <= 8
native_evidence = verify_native(native_path, native_receipt, allow_failed)
reference_evidence = verify_reference(reference_path, reference_receipt)
assert math.isclose(native_scale * 100, native_evidence['ScalePercent']), 'Native comparison scale differs from guest UI scale'
assert math.isclose(reference_scale, reference_evidence['ViewportCss']['DeviceScaleFactor']), 'Reference comparison scale differs from browser device scale'
with Image.open(native_path) as native_original, Image.open(reference_path) as reference_original:
native = roi(native_original, native_roi)
reference = roi(reference_original, reference_roi)
native_css = [native.width / native_scale, native.height / native_scale]
reference_css = [reference.width / reference_scale, reference.height / reference_scale]
assert all(math.isclose(a, b) for a, b in zip(native_css, reference_css)), ('ROI CSS sizes must match; no resize is performed', native_css, reference_css)
eligible = native.size == reference.size
result = {'Kind': 'ActualNativeAndWebsiteScreenshotComparison', 'ComparisonCompleted': True,
'Native': native_evidence, 'WebsiteReference': reference_evidence,
'NativeRoiPhysical': native_roi, 'ReferenceRoiPhysical': reference_roi,
'NativeScale': native_scale, 'ReferenceScale': reference_scale,
'ComparedCssSize': native_css, 'ComparisonEligible': eligible,
'OriginalPixelsPreserved': True, 'ImagesResized': False,
'HostImageCountsAsNativeProof': False,
'Meaning': 'Numerical pixel differences are observations. Completion does not mean visual parity or native compatibility passed.'}
output.mkdir(parents=True)
(output / 'NativeReceipt.json').write_bytes(native_receipt.read_bytes())
(output / 'ReferenceReceipt.json').write_bytes(reference_receipt.read_bytes())
native.save(output / 'NativeRoi.png'); reference.save(output / 'ReferenceRoi.png')
side = Image.new('RGB', (native.width + reference.width, max(native.height, reference.height) + 24), '#202020')
side.paste(native, (0, 24)); side.paste(reference, (native.width, 24))
labels = ImageDraw.Draw(side)
labels.text((4, 5), 'QEMU native framebuffer', fill='white')
labels.text((native.width + 4, 5), 'Website browser reference', fill='white')
side.save(output / 'SideBySide.png')
if eligible:
difference = ImageChops.difference(native, reference)
stats = ImageStat.Stat(difference)
maximum = ImageChops.lighter(ImageChops.lighter(difference.getchannel(0), difference.getchannel(1)), difference.getchannel(2))
unchanged = maximum.histogram()[0]
exact = difference.getbbox() is None
result.update({'ExactPixelMatch': exact, 'PixelParityAccepted': exact,
'MeanAbsoluteChannelError': sum(stats.mean) / 3,
'RootMeanSquareChannelError': math.sqrt(sum(value * value for value in stats.rms) / 3),
'ChannelMaximumError': [item[1] for item in stats.extrema],
'ChangedPixels': native.width * native.height - unchanged,
'ChangedPixelRatio': 1 - unchanged / (native.width * native.height)})
difference.save(output / 'Difference.png')
difference.point(lambda value: min(255, value * 4)).save(output / 'DifferenceAmplified4x.png')
else:
result.update({'ExactPixelMatch': None, 'PixelParityAccepted': False,
'Reason': 'CSS viewport matches but physical pixels differ because device scales differ; no resampling was used to mask the difference.'})
(output / 'Result.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n', 'utf-8')
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--Native', type=Path, required=True)
parser.add_argument('--NativeReceipt', type=Path, required=True)
parser.add_argument('--Reference', type=Path, required=True)
parser.add_argument('--ReferenceReceipt', type=Path, required=True)
parser.add_argument('--NativeRoi', type=int, nargs=4, required=True, metavar=('X', 'Y', 'WIDTH', 'HEIGHT'))
parser.add_argument('--ReferenceRoi', type=int, nargs=4, required=True, metavar=('X', 'Y', 'WIDTH', 'HEIGHT'))
parser.add_argument('--NativeScale', type=float, required=True)
parser.add_argument('--ReferenceScale', type=float, required=True)
parser.add_argument('--Output', type=Path, required=True)
parser.add_argument('--AllowFailedNative', action='store_true')
args = parser.parse_args()
result = compare(args.Native, args.NativeReceipt, args.Reference, args.ReferenceReceipt,
args.NativeRoi, args.ReferenceRoi, args.NativeScale, args.ReferenceScale, args.Output, args.AllowFailedNative)
print(json.dumps({'Output': str(args.Output.resolve()), 'ComparisonEligible': result['ComparisonEligible'],
'ExactPixelMatch': result['ExactPixelMatch'], 'MeanAbsoluteChannelError': result.get('MeanAbsoluteChannelError')}))
if __name__ == '__main__':
main()
"""Compare unmodified QEMU framebuffer screenshots with website browser references.
This postprocessor does not create a native screenshot, run a browser, replace
fonts, resize away differences or treat a host image as guest acceptance.
"""
import argparse
import hashlib
import json
import math
from pathlib import Path
from PIL import Image, ImageChops, ImageDraw, ImageStat
def digest(path):
return hashlib.sha256(Path(path).read_bytes()).hexdigest()
def read(path):
return json.loads(Path(path).read_text('utf-8-sig'))
def find_screenshots(value):
if isinstance(value, dict):
if isinstance(value.get('Path'), str) and isinstance(value.get('Sha256'), str):
yield value
for child in value.values():
yield from find_screenshots(child)
elif isinstance(value, list):
for child in value:
yield from find_screenshots(child)
def verify_native(path, receipt_path, allow_failed=False):
receipt = read(receipt_path)
assert receipt.get('Kind') == 'BareMetalNativeAotBootProof', 'Reference is not a native QEMU boot receipt'
assert receipt.get('Runtime', '').startswith('Cosmos NativeAOT'), 'Native runtime provenance missing'
assert receipt.get('QemuArgs') and '-qmp' in receipt['QemuArgs'], 'QEMU invocation is absent'
assert receipt.get('KernelSha256') and receipt.get('SourceSnapshotSha256') and receipt.get('ImageSha256'), 'Compiled image/source binding is absent'
snapshot = Path(receipt['SourceSnapshotPath'])
assert snapshot.exists() and digest(snapshot) == receipt['SourceSnapshotSha256'], 'Native compiled-source snapshot is missing or changed'
disk = Path(receipt['ImagePath'])
assert disk.exists() and digest(disk) == receipt['ImageSha256'], 'Native disk image differs from the accepted image hash'
if not allow_failed:
assert receipt.get('Passed') is True and receipt.get('ImageUnchanged') is True and receipt.get('QemuExitCode') == 0, 'Native acceptance failed; use --AllowFailedNative only for an explicitly failed diagnostic comparison'
image_hash = digest(path)
matching = [entry for entry in find_screenshots(receipt) if Path(entry['Path']).resolve() == path.resolve() and entry['Sha256'] == image_hash]
assert matching, 'Native screenshot path/hash does not appear in the QEMU receipt'
with Image.open(path) as image:
assert image.size == (receipt['NativeWidth'], receipt['NativeHeight']), 'Native image was resized or cropped before comparison'
return {'ReceiptPath': str(receipt_path.resolve()), 'ReceiptSha256': digest(receipt_path),
'ScreenshotPath': str(path.resolve()), 'ScreenshotSha256': image_hash,
'AcceptancePassed': receipt.get('Passed') is True,
'KernelSha256': receipt['KernelSha256'], 'ImageSha256': receipt['ImageSha256'],
'SourceSnapshotSha256': receipt['SourceSnapshotSha256'],
'NativeWidth': receipt['NativeWidth'], 'NativeHeight': receipt['NativeHeight'],
'MemoryMiB': receipt.get('MemoryMiB'), 'ScalePercent': receipt.get('ScalePercent'),
'SourceKind': 'Actual QEMU native framebuffer screendump'}
def verify_reference(path, receipt_path):
receipt = read(receipt_path)
assert receipt.get('Kind') == 'WebsiteBrowserReferenceScreenshot', 'Reference screenshot requires explicit website-browser provenance'
assert Path(receipt['ScreenshotPath']).resolve() == path.resolve(), 'Reference screenshot path differs from its receipt'
assert receipt['ScreenshotSha256'] == digest(path), 'Reference screenshot hash differs from its receipt'
assert len(receipt['WebsiteSourceManifestSha256']) == 64, 'Website source asset binding is absent'
viewport = receipt['ViewportCss']
assert viewport['Width'] > 0 and viewport['Height'] > 0 and 0 < viewport['DeviceScaleFactor'] <= 8
with Image.open(path) as image:
expected = (round(viewport['Width'] * viewport['DeviceScaleFactor']), round(viewport['Height'] * viewport['DeviceScaleFactor']))
assert image.size == expected, ('Reference pixels differ from the declared viewport/device scale', image.size, expected)
return {'ReceiptPath': str(receipt_path.resolve()), 'ReceiptSha256': digest(receipt_path),
'ScreenshotPath': str(path.resolve()), 'ScreenshotSha256': digest(path),
'WebsiteSourceManifestSha256': receipt['WebsiteSourceManifestSha256'], 'ViewportCss': viewport,
'SourceKind': 'Host website browser reference; never native guest acceptance',
'NativeGuestTested': False}
def roi(image, bounds):
x, y, width, height = bounds
assert min(x, y) >= 0 and min(width, height) > 0
assert x + width <= image.width and y + height <= image.height, ('ROI exceeds original screenshot bounds', bounds, image.size)
return image.crop((x, y, x + width, y + height)).convert('RGB')
def compare(native_path, native_receipt, reference_path, reference_receipt, native_roi, reference_roi, native_scale, reference_scale, output, allow_failed=False):
assert not output.exists(), 'Refusing to overwrite an existing comparison directory'
assert 0 < native_scale <= 8 and 0 < reference_scale <= 8
native_evidence = verify_native(native_path, native_receipt, allow_failed)
reference_evidence = verify_reference(reference_path, reference_receipt)
assert math.isclose(native_scale * 100, native_evidence['ScalePercent']), 'Native comparison scale differs from guest UI scale'
assert math.isclose(reference_scale, reference_evidence['ViewportCss']['DeviceScaleFactor']), 'Reference comparison scale differs from browser device scale'
with Image.open(native_path) as native_original, Image.open(reference_path) as reference_original:
native = roi(native_original, native_roi)
reference = roi(reference_original, reference_roi)
native_css = [native.width / native_scale, native.height / native_scale]
reference_css = [reference.width / reference_scale, reference.height / reference_scale]
assert all(math.isclose(a, b) for a, b in zip(native_css, reference_css)), ('ROI CSS sizes must match; no resize is performed', native_css, reference_css)
eligible = native.size == reference.size
result = {'Kind': 'ActualNativeAndWebsiteScreenshotComparison', 'ComparisonCompleted': True,
'Native': native_evidence, 'WebsiteReference': reference_evidence,
'NativeRoiPhysical': native_roi, 'ReferenceRoiPhysical': reference_roi,
'NativeScale': native_scale, 'ReferenceScale': reference_scale,
'ComparedCssSize': native_css, 'ComparisonEligible': eligible,
'OriginalPixelsPreserved': True, 'ImagesResized': False,
'HostImageCountsAsNativeProof': False,
'Meaning': 'Numerical pixel differences are observations. Completion does not mean visual parity or native compatibility passed.'}
output.mkdir(parents=True)
(output / 'NativeReceipt.json').write_bytes(native_receipt.read_bytes())
(output / 'ReferenceReceipt.json').write_bytes(reference_receipt.read_bytes())
native.save(output / 'NativeRoi.png'); reference.save(output / 'ReferenceRoi.png')
side = Image.new('RGB', (native.width + reference.width, max(native.height, reference.height) + 24), '#202020')
side.paste(native, (0, 24)); side.paste(reference, (native.width, 24))
labels = ImageDraw.Draw(side)
labels.text((4, 5), 'QEMU native framebuffer', fill='white')
labels.text((native.width + 4, 5), 'Website browser reference', fill='white')
side.save(output / 'SideBySide.png')
if eligible:
difference = ImageChops.difference(native, reference)
stats = ImageStat.Stat(difference)
maximum = ImageChops.lighter(ImageChops.lighter(difference.getchannel(0), difference.getchannel(1)), difference.getchannel(2))
unchanged = maximum.histogram()[0]
exact = difference.getbbox() is None
result.update({'ExactPixelMatch': exact, 'PixelParityAccepted': exact,
'MeanAbsoluteChannelError': sum(stats.mean) / 3,
'RootMeanSquareChannelError': math.sqrt(sum(value * value for value in stats.rms) / 3),
'ChannelMaximumError': [item[1] for item in stats.extrema],
'ChangedPixels': native.width * native.height - unchanged,
'ChangedPixelRatio': 1 - unchanged / (native.width * native.height)})
difference.save(output / 'Difference.png')
difference.point(lambda value: min(255, value * 4)).save(output / 'DifferenceAmplified4x.png')
else:
result.update({'ExactPixelMatch': None, 'PixelParityAccepted': False,
'Reason': 'CSS viewport matches but physical pixels differ because device scales differ; no resampling was used to mask the difference.'})
(output / 'Result.json').write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n', 'utf-8')
return result
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--Native', type=Path, required=True)
parser.add_argument('--NativeReceipt', type=Path, required=True)
parser.add_argument('--Reference', type=Path, required=True)
parser.add_argument('--ReferenceReceipt', type=Path, required=True)
parser.add_argument('--NativeRoi', type=int, nargs=4, required=True, metavar=('X', 'Y', 'WIDTH', 'HEIGHT'))
parser.add_argument('--ReferenceRoi', type=int, nargs=4, required=True, metavar=('X', 'Y', 'WIDTH', 'HEIGHT'))
parser.add_argument('--NativeScale', type=float, required=True)
parser.add_argument('--ReferenceScale', type=float, required=True)
parser.add_argument('--Output', type=Path, required=True)
parser.add_argument('--AllowFailedNative', action='store_true')
args = parser.parse_args()
result = compare(args.Native, args.NativeReceipt, args.Reference, args.ReferenceReceipt,
args.NativeRoi, args.ReferenceRoi, args.NativeScale, args.ReferenceScale, args.Output, args.AllowFailedNative)
print(json.dumps({'Output': str(args.Output.resolve()), 'ComparisonEligible': result['ComparisonEligible'],
'ExactPixelMatch': result['ExactPixelMatch'], 'MeanAbsoluteChannelError': result.get('MeanAbsoluteChannelError')}))
if __name__ == '__main__':
main()