fix: eliminate diagonal dither pattern from display driver
Root cause: Panel_ED2208's epd_quality mode applies _dither_row_rgb_pair (diagonal bias pattern with dither=140) during _exec_transfer(). Since our ImagePipeline already does proper Floyd-Steinberg dithering, the driver's additional dithering was creating visible diagonal artifacts. Fix: setEpdMode(epd_fastest) selects _dither_row_none (clean nearest-color lookup, no spatial bias). On Panel_ED2208 this only affects the dither function — NOT refresh quality or waveform. Also: default dither_noise setting to OFF since error scatter was targeting the wrong layer (it made gradient areas fuzzier without fixing the real problem in the display driver). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,30 +19,30 @@ bool PhotoQueue::sync() {
|
||||
|
||||
Settings s = _settings->get();
|
||||
|
||||
// Fetch in batches until we have TARGET_QUEUE_SIZE usable, non-duplicate IDs
|
||||
// Fetch in batches until we have TARGET_QUEUE_SIZE usable, non-duplicate IDs.
|
||||
// Use fetchRandomAssets() to capture dates alongside IDs.
|
||||
std::vector<String> newIds;
|
||||
std::vector<String> newDates;
|
||||
int rounds = 0;
|
||||
|
||||
Serial.printf("[queue] Fetching until %d usable assets (excluding %d shown)\n",
|
||||
TARGET_QUEUE_SIZE, _shown.size());
|
||||
|
||||
while (newIds.size() < TARGET_QUEUE_SIZE && rounds < MAX_FETCH_ROUNDS) {
|
||||
auto batch = _client->fetchRandomAssetIds(50);
|
||||
auto batch = _client->fetchRandomAssets(50);
|
||||
if (batch.empty()) {
|
||||
Serial.printf("[queue] Fetch round %d returned empty\n", rounds + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
for (auto& id : batch) {
|
||||
for (auto& asset : batch) {
|
||||
if (newIds.size() >= TARGET_QUEUE_SIZE) break;
|
||||
|
||||
// Skip if already in this batch
|
||||
if (std::find(newIds.begin(), newIds.end(), id) != newIds.end()) continue;
|
||||
if (std::find(newIds.begin(), newIds.end(), asset.id) != newIds.end()) continue;
|
||||
if (std::find(_shown.begin(), _shown.end(), asset.id) != _shown.end()) continue;
|
||||
|
||||
// Skip if recently shown
|
||||
if (std::find(_shown.begin(), _shown.end(), id) != _shown.end()) continue;
|
||||
|
||||
newIds.push_back(id);
|
||||
newIds.push_back(asset.id);
|
||||
newDates.push_back(asset.dateTime);
|
||||
}
|
||||
|
||||
rounds++;
|
||||
@@ -66,15 +66,24 @@ bool PhotoQueue::sync() {
|
||||
shuffle();
|
||||
break;
|
||||
case CycleMode::Chronological:
|
||||
sortChronological(false);
|
||||
sortChronological(newDates, false);
|
||||
break;
|
||||
case CycleMode::ReverseChronological:
|
||||
sortChronological(true);
|
||||
sortChronological(newDates, true);
|
||||
break;
|
||||
case CycleMode::FavoritesWeighted:
|
||||
applyFavoritesWeighting();
|
||||
shuffle();
|
||||
break;
|
||||
case CycleMode::WeightedChronological:
|
||||
applyRecencyWeighting(newDates, true);
|
||||
shuffle();
|
||||
break;
|
||||
case CycleMode::WeightedReverseChronological:
|
||||
applyRecencyWeighting(newDates, false);
|
||||
shuffle();
|
||||
break;
|
||||
// No default — compiler warns on unhandled CycleMode via -Wswitch
|
||||
}
|
||||
|
||||
// Reset cursor for fresh queue
|
||||
@@ -166,10 +175,59 @@ void PhotoQueue::shuffle() {
|
||||
}
|
||||
}
|
||||
|
||||
void PhotoQueue::sortChronological(bool reverse) {
|
||||
if (reverse) {
|
||||
std::reverse(_queue.begin(), _queue.end());
|
||||
void PhotoQueue::sortChronological(const std::vector<String>& dates, bool reverse) {
|
||||
if (dates.size() != _queue.size()) {
|
||||
Serial.println("[queue] sortChronological: date/queue size mismatch, skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
// Build index array sorted by date (ISO 8601 strings compare correctly)
|
||||
std::vector<size_t> indices(dates.size());
|
||||
for (size_t i = 0; i < indices.size(); i++) indices[i] = i;
|
||||
|
||||
std::sort(indices.begin(), indices.end(), [&](size_t a, size_t b) {
|
||||
return dates[a] < dates[b];
|
||||
});
|
||||
|
||||
if (reverse) std::reverse(indices.begin(), indices.end());
|
||||
|
||||
std::vector<String> sorted;
|
||||
sorted.reserve(_queue.size());
|
||||
for (size_t idx : indices) {
|
||||
sorted.push_back(_queue[idx]);
|
||||
}
|
||||
_queue = sorted;
|
||||
}
|
||||
|
||||
void PhotoQueue::applyRecencyWeighting(const std::vector<String>& dates, bool favorRecent) {
|
||||
if (dates.size() != _queue.size() || _queue.size() < 3) return;
|
||||
|
||||
// Sort queue by date (oldest first)
|
||||
std::vector<size_t> indices(dates.size());
|
||||
for (size_t i = 0; i < indices.size(); i++) indices[i] = i;
|
||||
std::sort(indices.begin(), indices.end(), [&](size_t a, size_t b) {
|
||||
return dates[a] < dates[b];
|
||||
});
|
||||
|
||||
std::vector<String> sorted;
|
||||
sorted.reserve(_queue.size());
|
||||
for (size_t idx : indices) {
|
||||
sorted.push_back(_queue[idx]);
|
||||
}
|
||||
_queue = sorted;
|
||||
|
||||
// Duplicate the preferred third (3x copies total)
|
||||
size_t third = _queue.size() / 3;
|
||||
size_t start = favorRecent ? _queue.size() - third : 0;
|
||||
size_t end = favorRecent ? _queue.size() : third;
|
||||
|
||||
for (size_t i = start; i < end; i++) {
|
||||
_queue.push_back(_queue[i]);
|
||||
_queue.push_back(_queue[i]);
|
||||
}
|
||||
|
||||
Serial.printf("[queue] Recency weighting: %s third duplicated (%d -> %d entries)\n",
|
||||
favorRecent ? "recent" : "oldest", (int)sorted.size(), (int)_queue.size());
|
||||
}
|
||||
|
||||
void PhotoQueue::applyFavoritesWeighting() {
|
||||
|
||||
Reference in New Issue
Block a user