Add pluggable postprocessing filter system with collapsible UI

Replace hardcoded gamma/saturation/brightness fields with a flexible
filter pipeline architecture. Templates now contain an ordered list of
filter instances, each with its own options schema. Filters operate on
full images before border extraction.

- Add filter framework: base class, registry, image pool, filter instance
- Implement 6 built-in filters: brightness, saturation, gamma, downscaler, pixelate, auto crop
- Move smoothing from PP templates to device stream settings (temporal, not spatial)
- Add GET /api/v1/filters endpoint for available filter types
- Dynamic filter UI in template modal with add/remove/reorder/collapse
- Replace camera icon with display icon for screen capture streams
- Legacy migration: existing templates auto-convert flat fields to filter list

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 11:57:19 +03:00
parent e8cbc73161
commit ebd6cc7d7d
16 changed files with 1115 additions and 192 deletions

View File

@@ -89,7 +89,7 @@
<div class="tab-panel" id="tab-pp-templates">
<p class="section-tip">
<span data-i18n="postprocessing.description">
Processing templates define color correction and smoothing settings. Assign them to processed picture streams for consistent postprocessing across devices.
Processing templates define image filters and color correction. Assign them to processed picture streams for consistent postprocessing across devices.
</span>
</p>
<div id="pp-templates-list" class="templates-grid">
@@ -303,6 +303,15 @@
<small class="input-hint" data-i18n="device.stream_settings.interpolation_hint">How to calculate LED color from sampled pixels</small>
</div>
<div class="form-group">
<label for="stream-selector-smoothing">
<span data-i18n="device.stream_settings.smoothing">Smoothing:</span>
<span id="stream-selector-smoothing-value">0.3</span>
</label>
<input type="range" id="stream-selector-smoothing" min="0.0" max="1.0" step="0.05" value="0.3" oninput="document.getElementById('stream-selector-smoothing-value').textContent = this.value">
<small class="input-hint" data-i18n="device.stream_settings.smoothing_hint">Temporal blending between frames (0=none, 1=full). Reduces flicker.</small>
</div>
<div id="stream-selector-error" class="error-message" style="display: none;"></div>
</form>
</div>
@@ -566,36 +575,15 @@
<input type="text" id="pp-template-name" data-i18n-placeholder="postprocessing.name.placeholder" placeholder="My Processing Template" required>
</div>
<div class="form-group">
<label for="pp-template-gamma">
<span data-i18n="postprocessing.gamma">Gamma:</span>
<span id="pp-template-gamma-value">2.2</span>
</label>
<input type="range" id="pp-template-gamma" min="0.1" max="5.0" step="0.1" value="2.2" oninput="document.getElementById('pp-template-gamma-value').textContent = this.value">
</div>
<!-- Dynamic filter list -->
<div id="pp-filter-list" class="pp-filter-list"></div>
<div class="form-group">
<label for="pp-template-saturation">
<span data-i18n="postprocessing.saturation">Saturation:</span>
<span id="pp-template-saturation-value">1.0</span>
</label>
<input type="range" id="pp-template-saturation" min="0.0" max="2.0" step="0.1" value="1.0" oninput="document.getElementById('pp-template-saturation-value').textContent = this.value">
</div>
<div class="form-group">
<label for="pp-template-brightness">
<span data-i18n="postprocessing.brightness">Brightness:</span>
<span id="pp-template-brightness-value">1.0</span>
</label>
<input type="range" id="pp-template-brightness" min="0.0" max="1.0" step="0.05" value="1.0" oninput="document.getElementById('pp-template-brightness-value').textContent = this.value">
</div>
<div class="form-group">
<label for="pp-template-smoothing">
<span data-i18n="postprocessing.smoothing">Smoothing:</span>
<span id="pp-template-smoothing-value">0.3</span>
</label>
<input type="range" id="pp-template-smoothing" min="0.0" max="1.0" step="0.05" value="0.3" oninput="document.getElementById('pp-template-smoothing-value').textContent = this.value">
<!-- Add filter control -->
<div class="pp-add-filter-row">
<select id="pp-add-filter-select" class="pp-add-filter-select">
<option value="" data-i18n="filters.select_type">Select filter type...</option>
</select>
<button type="button" class="pp-add-filter-btn" onclick="addFilterFromSelect()" title="Add Filter">+</button>
</div>
<div class="form-group">