shot them all: compare shots
This commit is contained in:
parent
bb5e118f1b
commit
129c1ecd58
|
@ -0,0 +1,120 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Compare desktop/mobile shots of all themes from publik-base-theme.
|
||||
#
|
||||
# Usage:
|
||||
# --input1-directory -- directory to find version 1 screenshots
|
||||
# --input2-directory -- directory to find version 2 screenshots
|
||||
# --output-directory DIRECTORY -- directory to store comparison results, defaults to results/
|
||||
# --overlay OVERLAY -- limit to given overlay ("none" to exclude overlays)
|
||||
# --theme THEME -- theme to shot (substring match)
|
||||
#
|
||||
# Example:
|
||||
# hobo-manage tenant_command runscript compare_them_all.py -d hobo.fred.local.0d.be
|
||||
#
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
from hobo.theme.utils import get_themes
|
||||
from django.template import Template, Context
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--input1-directory', dest='input1_directory', type=str, required=True)
|
||||
parser.add_argument('--input2-directory', dest='input2_directory', type=str, required=True)
|
||||
parser.add_argument('--output-directory', dest='output_directory', type=str, default='results')
|
||||
parser.add_argument('--overlay', dest='overlay', type=str, default='none')
|
||||
parser.add_argument('--theme', dest='theme', type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.output_directory):
|
||||
os.mkdir(args.output_directory)
|
||||
|
||||
|
||||
result = []
|
||||
|
||||
for theme in get_themes():
|
||||
theme_id = theme['id']
|
||||
if args.overlay == 'all':
|
||||
pass
|
||||
elif args.overlay == 'none' and not theme.get('overlay'):
|
||||
pass
|
||||
elif args.overlay == theme.get('overlay'):
|
||||
pass
|
||||
else:
|
||||
continue
|
||||
if args.theme and args.theme not in theme['id']:
|
||||
continue
|
||||
sys.stderr.write("%-25s" % theme_id)
|
||||
shots = [
|
||||
'desktop',
|
||||
'mobile',
|
||||
'mobile-horizontal',
|
||||
]
|
||||
|
||||
def process_region(image, x, y, width, height):
|
||||
region_total = 0
|
||||
# This can be used as the sensitivity factor, the larger it is the less sensitive the comparison
|
||||
factor = 100
|
||||
for coordinate_y in range(y, y + height):
|
||||
for coordinate_x in range(x, x + width):
|
||||
try:
|
||||
pixel = image.getpixel((coordinate_x, coordinate_y))
|
||||
region_total += sum(pixel) / 4
|
||||
except Exception:
|
||||
return
|
||||
return region_total / factor
|
||||
|
||||
theme_data = {
|
||||
'label': theme['label'],
|
||||
'shots': [],
|
||||
}
|
||||
|
||||
for shot in shots:
|
||||
filepath_1 = os.path.join(args.input1_directory, '%s-%s.png' % (theme_id, shot))
|
||||
filepath_2 = os.path.join(args.input2_directory, '%s-%s.png' % (theme_id, shot))
|
||||
filepath_output = os.path.join(args.output_directory, '%s-%s.png' % (theme_id, shot))
|
||||
try:
|
||||
screenshot_1 = Image.open(filepath_1)
|
||||
screenshot_2 = Image.open(filepath_2)
|
||||
except FileNotFoundError:
|
||||
continue
|
||||
width, height = screenshot_1.size
|
||||
|
||||
columns = 60
|
||||
rows = 80
|
||||
screen_width, screen_height = screenshot_1.size
|
||||
|
||||
block_width = ((screen_width - 1) // columns) + 1
|
||||
block_height = ((screen_height - 1) // rows) + 1
|
||||
|
||||
for y in range(0, screen_height, block_height + 1):
|
||||
for x in range(0, screen_width, block_width + 1):
|
||||
region_1 = process_region(screenshot_1, x, y, block_width, block_height)
|
||||
region_2 = process_region(screenshot_2, x, y, block_width, block_height)
|
||||
|
||||
if region_1 is not None and region_2 is not None and region_1 != region_2:
|
||||
draw = ImageDraw.Draw(screenshot_1)
|
||||
draw.rectangle((x, y, x + block_width, y + block_height), outline="red")
|
||||
|
||||
screenshot_1.save(filepath_output)
|
||||
theme_data['shots'].append({
|
||||
'label': shot,
|
||||
'width': width,
|
||||
'height': height,
|
||||
'before': '../%s' % filepath_1,
|
||||
'after': '../%s' % filepath_2,
|
||||
'diff': '../%s' % filepath_output,
|
||||
})
|
||||
result.append(theme_data)
|
||||
|
||||
sys.stderr.write(u'\n')
|
||||
|
||||
with open('index_template.html', 'r') as f:
|
||||
template = Template(f.read())
|
||||
context = Context({'themes': result})
|
||||
html = template.render(context)
|
||||
|
||||
with open(os.path.join(args.output_directory, 'index.html'), 'w') as f:
|
||||
f.write(html)
|
|
@ -0,0 +1,26 @@
|
|||
<html>
|
||||
<meta charset="UTF-8">
|
||||
<head>
|
||||
<script src="https://code.jquery.com/jquery-3.6.0.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://www.jqueryscript.net/demo/before-after-image-viewer/beforeafter.jquery-1.0.0.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
{% for theme in themes %}
|
||||
<h3>{{ theme.label }}</h3>
|
||||
{% for shot in theme.shots %}
|
||||
<h4>{{ shot.label }}</h4>
|
||||
<div class="beforeAfter" style="width: {{ shot.width }}px; height: {{ shot.height }}px">
|
||||
<img src="{{ shot.before }}"/>
|
||||
<img src="{{ shot.after }}"/>
|
||||
</div>
|
||||
<h5>Diff</h5>
|
||||
<img src="{{ shot.diff }}" />
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<script>
|
||||
$(function(){
|
||||
$('.beforeAfter').beforeAfter();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -3,14 +3,15 @@
|
|||
# Take desktop/mobile shots of all themes from publik-base-theme.
|
||||
#
|
||||
# Usage:
|
||||
# --directory DIRECTORY -- directory to store shots, defaults to shots/
|
||||
# --no-headless -- run in an actual browser window
|
||||
# --no-memcached -- do not restart memcached after theme changes
|
||||
# --overlay OVERLAY -- limit to given overlay ("none" to exclude overlays)
|
||||
# --reshot -- retake existing shots
|
||||
# --timeout TIMEOUT -- timeout between shots (time for hobo deploy)
|
||||
# --theme THEME -- theme to shot (substring match)
|
||||
# URL -- specific URL to shot
|
||||
# --directory DIRECTORY -- directory to store shots, defaults to shots/
|
||||
# --no-headless -- run in an actual browser window
|
||||
# --no-memcached -- do not restart memcached after theme changes
|
||||
# --overlay OVERLAY -- limit to given overlay ("none" to exclude overlays)
|
||||
# --reshot -- retake existing shots
|
||||
# --timeout TIMEOUT -- timeout between shots (time for hobo deploy)
|
||||
# --cell-timeout TIMEOUT -- timeout after url loading (time for ajax cells loading)
|
||||
# --theme THEME -- theme to shot (substring match)
|
||||
# URL -- specific URL to shot
|
||||
#
|
||||
# Example:
|
||||
# hobo-manage tenant_command runscript shot_them_all.py -d hobo.fred.local.0d.be
|
||||
|
@ -31,9 +32,10 @@ parser.add_argument('--no-memcached', dest='no_memcached', action='store_true')
|
|||
parser.add_argument('--overlay', dest='overlay', type=str, default='none')
|
||||
parser.add_argument('--reshot', dest='reshot', action='store_true')
|
||||
parser.add_argument('--timeout', dest='timeout', type=int, default=20) # seconds
|
||||
parser.add_argument('--cell-timeout', dest='cell_timeout', type=int, default=0) # seconds
|
||||
parser.add_argument('--theme', dest='theme', type=str)
|
||||
parser.add_argument('url', metavar='URL', type=str, nargs='?',
|
||||
default='https://auquo.fred.local.0d.be/contactez-nous/inscription-sur-les-listes/')
|
||||
default='https://auquo.fred.local.0d.be/contactez-nous/inscription-sur-les-listes/')
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.directory):
|
||||
|
@ -54,7 +56,7 @@ for theme in get_themes():
|
|||
pass
|
||||
else:
|
||||
continue
|
||||
if args.theme and not args.theme in theme['id']:
|
||||
if args.theme and args.theme not in theme['id']:
|
||||
continue
|
||||
sys.stderr.write("%-25s" % theme_id)
|
||||
shots = [
|
||||
|
@ -75,9 +77,18 @@ for theme in get_themes():
|
|||
if not args.no_memcached:
|
||||
os.system('sudo service memcached restart')
|
||||
browser.get(args.url)
|
||||
for i in range(args.cell_timeout):
|
||||
sys.stderr.write('.')
|
||||
time.sleep(1)
|
||||
|
||||
def size(value):
|
||||
return browser.execute_script('return document.body.parentNode.scroll' + value)
|
||||
|
||||
for shot in shots:
|
||||
browser.set_window_size(shot[0], shot[1])
|
||||
browser.set_window_size(shot[0], size('Height'))
|
||||
browser.save_screenshot(os.path.join(args.directory, '%s-%s.png' % (theme_id, shot[2])))
|
||||
|
||||
sys.stderr.write(u' 📸 \n')
|
||||
|
||||
browser.close()
|
||||
|
|
Loading…
Reference in New Issue