PhotoDithering

This commit is contained in:
2026-09-16 15:01:06 +10:00
parent 3b2a399769
commit d998b6731e
7 changed files with 304 additions and 3 deletions
+51
View File
@@ -833,6 +833,24 @@ function openSiteModal(site) {
</div>
<p class="hint" id="badge-warning" hidden></p>
<label class="inline"><input type="checkbox" name="showPhoto" id="badge-photo" ${site.badge.showPhoto ? 'checked' : ''}> Include the visitor's photo</label>
<div class="photo-tuning">
<label class="modal-field"><span>Photo rendering</span>
<select name="photoMode" id="photo-mode">
<option value="dither" ${site.photo.mode === 'dither' ? 'selected' : ''}>Error diffusion — best for faces</option>
<option value="threshold" ${site.photo.mode === 'threshold' ? 'selected' : ''}>Hard threshold — crisp, loses detail</option>
<option value="none" ${site.photo.mode === 'none' ? 'selected' : ''}>Leave it to the printer driver</option>
</select></label>
<label class="modal-field"><span>Threshold <b id="photo-threshold-value">${site.photo.threshold}</b>%</span>
<input type="range" name="photoThreshold" id="photo-threshold" min="5" max="95" step="5" value="${site.photo.threshold}"></label>
<label class="modal-field"><span>Contrast <b id="photo-contrast-value">${site.photo.contrast}</b></span>
<input type="range" name="photoContrast" id="photo-contrast" min="-50" max="100" step="10" value="${site.photo.contrast}"></label>
<button type="button" class="ghost" id="photo-preview-btn">Preview the photo settings</button>
<p class="hint">The printer has one bit per dot, so a photo has to become pure black and
white. A hard threshold turns a face into solid blocks; error diffusion scatters the
rounding error into neighbouring dots and keeps the tones readable. Higher threshold means
darker. The preview shows the most recent real visitor photo.</p>
<img id="photo-preview" class="photo-preview" alt="" hidden>
</div>
<label class="inline"><input type="checkbox" name="accent" id="badge-accent" ${site.badge.accent ? 'checked' : ''}> Print the heading and the no-check warning in red</label>
<p class="hint" id="accent-note"></p>
<p class="hint">Two-colour printing is much slower than black alone.</p>
@@ -929,6 +947,11 @@ function openSiteModal(site) {
rotate: Number(data.printerRotate) || 0,
label: data.printerLabel,
},
photo: {
mode: data.photoMode,
threshold: Number(data.photoThreshold),
contrast: Number(data.photoContrast),
},
branding: {
brand: data.brand || null,
signout: data.signout || null,
@@ -1037,6 +1060,34 @@ function wireBannerEditor() {
[pageInput, textInput].forEach((el) => el.addEventListener('input', showContrast));
showContrast();
// Live readouts for the halftone sliders, and a preview on demand. The preview
// is not automatic: it re-renders a real photo five times and is not free.
const modeSel = $('#photo-mode');
const thr = $('#photo-threshold');
const con = $('#photo-contrast');
const refreshLabels = () => {
$('#photo-threshold-value').textContent = thr.value;
$('#photo-contrast-value').textContent = con.value;
const off = modeSel.value === 'none';
thr.disabled = off;
con.disabled = off;
};
[thr, con].forEach((el) => el?.addEventListener('input', refreshLabels));
modeSel?.addEventListener('change', refreshLabels);
refreshLabels();
$('#photo-preview-btn')?.addEventListener('click', () => {
const img = $('#photo-preview');
const params = new URLSearchParams({
mode: modeSel.value,
threshold: thr.value,
contrast: con.value,
t: Date.now(),
});
img.src = `/admin/api/sites/${site.id}/photo-preview?${params}`;
img.hidden = false;
});
// Red is only possible on the two-colour roll, so say so as the two settings change.
const accentBox = $('#badge-accent');
const rollSelect = $('#modal-form [name="printerLabel"]');