Skip to content

Commit a0b04e9

Browse files
committed
Image comparison tool so we can more easily review visual diffs between
current and reference builds.
1 parent 3e13629 commit a0b04e9

File tree

2 files changed

+283
-0
lines changed

2 files changed

+283
-0
lines changed

tools/compare/compare.js

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
// Author: Ron B. Yeh
2+
3+
// See: https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API
4+
5+
// store a reference to our file handle
6+
let fileHandle;
7+
8+
// Images we discovered in the vexflow/build/images/current folder.
9+
const currentImages = {}; // FileSystemFileHandle
10+
const currentImagesNames = [];
11+
// Images we discovered in the vexflow/build/images/reference folder.
12+
const referenceImages = {}; // FileSystemFileHandle
13+
const referenceImagesNames = [];
14+
15+
let filterStrings = [];
16+
17+
async function getFile() {
18+
// open file picker
19+
[fileHandle] = await window.showOpenFilePicker();
20+
21+
if (fileHandle.kind === 'file') {
22+
// run file code
23+
} else if (fileHandle.kind === 'directory') {
24+
// run directory code
25+
}
26+
}
27+
28+
const pickerOpts = {
29+
types: [
30+
{
31+
description: 'Images',
32+
accept: {
33+
'image/*': ['.png', '.gif', '.jpeg', '.jpg'],
34+
},
35+
},
36+
],
37+
excludeAcceptAllOption: true,
38+
multiple: false,
39+
};
40+
41+
async function openTheFolder() {
42+
// Ask the user to choose a folder.
43+
// Window.showDirectoryPicker() returns a handle for the directory.
44+
try {
45+
const dirHandle = await window.showDirectoryPicker();
46+
listDirectoryContents(dirHandle);
47+
} catch (err) {
48+
// console.error(err.name, err.message); // AbortError The user aborted a request.
49+
console.log('The user closed the dialog without selecting a folder.');
50+
}
51+
}
52+
53+
function addListeners() {
54+
document.addEventListener('dragover', (e) => {
55+
// Prevent navigation.
56+
e.preventDefault();
57+
});
58+
59+
document.addEventListener('drop', async (e) => {
60+
// Prevent navigation.
61+
e.preventDefault();
62+
// Process all of the items.
63+
for (const item of e.dataTransfer.items) {
64+
// Careful: `kind` will be 'file' for both file _and_ directory entries.
65+
if (item.kind === 'file') {
66+
const handle = await item.getAsFileSystemHandle();
67+
if (handle.kind === 'directory') {
68+
listDirectoryContents(handle);
69+
}
70+
}
71+
}
72+
});
73+
}
74+
75+
async function listDirectoryContents(dirHandle) {
76+
// Get a list of entries in the directory.
77+
const entries = await dirHandle.values();
78+
for await (const entry of entries) {
79+
if (entry.kind === 'directory') {
80+
if (entry.name === 'current') {
81+
await getImagesFromCurrentFolder(entry);
82+
} else if (entry.name === 'reference') {
83+
await getImagesFromReferenceFolder(entry);
84+
}
85+
}
86+
}
87+
88+
populateSelectBoxes();
89+
}
90+
91+
async function getImagesFromCurrentFolder(dirHandle) {
92+
const entries = await dirHandle.values();
93+
for await (const entry of entries) {
94+
const fileName = entry.name;
95+
currentImages[fileName] = entry;
96+
currentImagesNames.push(fileName);
97+
}
98+
99+
currentImagesNames.sort();
100+
}
101+
async function getImagesFromReferenceFolder(dirHandle) {
102+
const entries = await dirHandle.values();
103+
for await (const entry of entries) {
104+
const fileName = entry.name;
105+
referenceImages[fileName] = entry;
106+
referenceImagesNames.push(fileName);
107+
}
108+
109+
referenceImagesNames.sort();
110+
}
111+
112+
function populateSelectBoxes() {
113+
// Left Side: Current Images
114+
document.getElementById('currentImages').innerHTML = buildOptionsHTMLString(currentImagesNames, 'current_');
115+
116+
// Right Side: Reference Images
117+
document.getElementById('referenceImages').innerHTML = buildOptionsHTMLString(referenceImagesNames, 'reference_');
118+
}
119+
120+
// Uses filterStrings to filter the list of images.
121+
// Returns a string of HTML <option></option> that match the filter.
122+
function buildOptionsHTMLString(imageNamesArray, idPrefix) {
123+
let options = '';
124+
125+
for (let imageName of imageNamesArray) {
126+
const lowerCaseImageName = imageName.toLowerCase();
127+
const allFiltersMatch = filterStrings.every((filter) => lowerCaseImageName.includes(filter));
128+
if (allFiltersMatch) {
129+
options += `<option id="${idPrefix + imageName}" value="${imageName}">${imageName}</option>`;
130+
}
131+
}
132+
return options;
133+
}
134+
135+
let timeoutID = 0;
136+
function filterResults() {
137+
let filterString = document.getElementById('filter').value;
138+
filterString = filterString.toLowerCase().replace(/,/g, ' ');
139+
filterStrings = filterString.split(' ');
140+
console.log(filterStrings);
141+
142+
// Filter the list with at least a 400ms delay after the last letter was typed.
143+
clearTimeout(timeoutID);
144+
timeoutID = setTimeout(() => {
145+
populateSelectBoxes();
146+
}, 400);
147+
}
148+
149+
async function selectedCurrentImage() {
150+
let selectBox = document.getElementById('currentImages');
151+
152+
// Select the corresponding item on the right side.
153+
let selectedImageFileName = selectBox.options[selectBox.selectedIndex].value;
154+
let referenceImage = document.getElementById('reference_' + selectedImageFileName);
155+
referenceImage.selected = true;
156+
157+
showImages(selectedImageFileName);
158+
}
159+
160+
async function selectedReferenceImage() {
161+
let selectBox = document.getElementById('referenceImages');
162+
163+
// Select the corresponding item on the left side.
164+
let selectedImageFileName = selectBox.options[selectBox.selectedIndex].value;
165+
let currentImage = document.getElementById('current_' + selectedImageFileName);
166+
currentImage.selected = true;
167+
168+
showImages(selectedImageFileName);
169+
}
170+
171+
async function showImages(selectedImageFileName) {
172+
const currentFileHandle = currentImages[selectedImageFileName];
173+
const referenceFileHandle = referenceImages[selectedImageFileName];
174+
175+
const cFile = await currentFileHandle.getFile();
176+
const rFile = await referenceFileHandle.getFile();
177+
178+
const cURL = URL.createObjectURL(cFile);
179+
const rURL = URL.createObjectURL(rFile);
180+
181+
const imagesContainer = document.getElementById('images');
182+
183+
// Clear the images container.
184+
while (imagesContainer.firstChild) {
185+
imagesContainer.removeChild(imagesContainer.firstChild);
186+
}
187+
188+
// Add the two new images.
189+
const imgC = document.createElement('img');
190+
imgC.src = cURL;
191+
imagesContainer.appendChild(imgC);
192+
const imgR = document.createElement('img');
193+
imgR.src = rURL;
194+
imagesContainer.appendChild(imgR);
195+
}

tools/compare/index.html

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="compare.js"></script>
5+
<style>
6+
body {
7+
font-family: sans-serif;
8+
font-size: 24px;
9+
margin: 40px;
10+
color: #111;
11+
background-color: #f3faff;
12+
}
13+
button {
14+
font-size: 18px;
15+
padding: 0.5em 1em;
16+
border: 1px solid #ccc;
17+
border-radius: 0.5em;
18+
background: #eee;
19+
cursor: pointer;
20+
}
21+
button:hover {
22+
background: #dff;
23+
}
24+
button:active {
25+
background: #6cc;
26+
}
27+
28+
.centered {
29+
display: flex;
30+
justify-content: center;
31+
}
32+
33+
.centered select {
34+
margin: 0;
35+
width: 45%;
36+
text-indent: 10px;
37+
font-size: 13px;
38+
font-family: ui-monospace, Menlo, Monaco, 'Droid Sans Mono', 'Courier New', monospace;
39+
overflow: auto;
40+
height: 200px;
41+
}
42+
43+
#filter {
44+
width: 90%;
45+
padding: 12px 20px;
46+
margin: 12px 0;
47+
font-size: 16px;
48+
box-sizing: border-box;
49+
}
50+
51+
#images {
52+
margin-top: 20px;
53+
}
54+
</style>
55+
<script>
56+
document.addEventListener('DOMContentLoaded', function () {
57+
addListeners();
58+
});
59+
</script>
60+
</head>
61+
<body>
62+
<p>
63+
Open the <b><code>vexflow/build/images/</code></b> folder, which contains <code>current, diff, reference</code>.
64+
</p>
65+
<p>When the pop-up appears, click <b>View Files</b>.</p>
66+
<p><button onclick="openTheFolder()">Open Folder</button></p>
67+
<p>
68+
You can also drag and drop the <b><code>vexflow/build/images/</code></b> folder onto this page.
69+
</p>
70+
<div class="centered">
71+
<input
72+
id="filter"
73+
type="text"
74+
placeholder="Search terms separated by spaces..."
75+
onkeyup="filterResults()"
76+
/><br />
77+
</div>
78+
<div class="centered">
79+
<select id="currentImages" size="2" onchange="if (this.selectedIndex > -1) selectedCurrentImage();"></select>
80+
<select id="referenceImages" size="5" onchange="if (this.selectedIndex > -1) selectedReferenceImage();"></select>
81+
</div>
82+
<p>
83+
LEFT / RIGHT arrow keys flip between current / reference images.<br />UP / DOWN arrow keys selects the next /
84+
previous image to compare.<br />ESC shows images side by side.
85+
</p>
86+
<div id="images" class="centered"></div>
87+
</body>
88+
</html>

0 commit comments

Comments
 (0)