Zuschnitt als Regel statt als gespeichertes Bild
Die Sichtung merkt sich jetzt, WO der Vogel im Foto sitzt (vier Zahlen); die Anzeige schneidet daraus bei jedem Aufruf. Eine geaenderte Regel wirkt damit sofort auf den ganzen Bestand. Altbestand faellt auf das alte Bild zurueck.
This commit is contained in:
1 parent
f572c20080
commit
a97f066e92
14 files changed
+421
-19
No files matched your search
@@ -714,6 +714,33 @@ class Sighting(models.Model):
|
||||
votes = models.JSONField(default=dict, blank=True)
|
||||
mean_bird_score = models.FloatField(default=0.0)
|
||||
|
||||
# 🔑 **Wo der Vogel im Bild sitzt — als vier Zahlen, nicht als zweites
|
||||
# Bild.**
|
||||
#
|
||||
# Bis zum 14.10.2026 speicherte das Projekt neben dem Foto einen fertig
|
||||
# geschnittenen Ausschnitt. Der Zuschnitt war damit für immer festgelegt:
|
||||
# Eine bessere Regel half nur neuen Sichtungen, und alle vorhandenen
|
||||
# mussten einzeln nachgezogen werden.
|
||||
#
|
||||
# Aufgefallen ist es an Handyfotos. Dort ist der Vogel klein im Bild, der
|
||||
# enge Ausschnitt entsprechend schmal und hoch — und in einer quadratischen
|
||||
# Kachel blieb davon die Bauchmitte übrig, formatfüllend und unscharf.
|
||||
# Christians Urteil, und es war das richtige: *„Mir geht es darum, dass ein
|
||||
# Nutzer das so nicht verwenden kann."*
|
||||
#
|
||||
# Mit den Koordinaten ist der Zuschnitt eine **Regel statt einer Datei**:
|
||||
# Die Anzeige schneidet bei jedem Aufruf neu aus dem Original. Eine
|
||||
# geänderte Regel wirkt sofort auf den gesamten Bestand, Website und App
|
||||
# dürfen verschieden schneiden, und niemand braucht den Detector dafür.
|
||||
#
|
||||
# Werte sind relativ (0..1), damit sie eine Verkleinerung des Originals
|
||||
# überleben. Leer heißt: von vor dieser Änderung — dann gilt weiter das
|
||||
# gespeicherte ``crop``.
|
||||
box_x0 = models.FloatField("Vogel: links", null=True, blank=True)
|
||||
box_y0 = models.FloatField("Vogel: oben", null=True, blank=True)
|
||||
box_x1 = models.FloatField("Vogel: rechts", null=True, blank=True)
|
||||
box_y1 = models.FloatField("Vogel: unten", null=True, blank=True)
|
||||
|
||||
is_first_of_species = models.BooleanField(default=False)
|
||||
xp_awarded = models.PositiveIntegerField(default=0)
|
||||
|
||||
@@ -743,6 +770,34 @@ class Sighting(models.Model):
|
||||
"""Handkorrektur schlägt Modell."""
|
||||
return self.confirmed_species or self.species
|
||||
|
||||
@property
|
||||
def vogel_box(self) -> tuple[float, float, float, float] | None:
|
||||
"""Wo der Vogel sitzt, relativ — oder ``None`` bei Altbestand."""
|
||||
werte = (self.box_x0, self.box_y0, self.box_x1, self.box_y1)
|
||||
if any(w is None for w in werte):
|
||||
return None
|
||||
x0, y0, x1, y1 = werte
|
||||
if x1 <= x0 or y1 <= y0:
|
||||
return None
|
||||
return (x0, y0, x1, y1)
|
||||
|
||||
@property
|
||||
def kachel_url(self) -> str:
|
||||
"""Das Bild für eine Kachel: aus dem Original geschnitten.
|
||||
|
||||
🔑 Rückfall auf das gespeicherte ``crop`` für Sichtungen von vor der
|
||||
Umstellung, danach auf das Original. So bleibt jede alte Sammlung
|
||||
anzeigbar, ohne dass jemand etwas nachziehen muss.
|
||||
"""
|
||||
from .zuschnitt import adresse
|
||||
|
||||
neu = adresse(self)
|
||||
if neu:
|
||||
return neu
|
||||
if self.crop:
|
||||
return self.crop.url
|
||||
return self.image.url if self.image else ""
|
||||
|
||||
@property
|
||||
def duration_s(self) -> float:
|
||||
return (self.ended_at - self.started_at).total_seconds()
|
||||
|
||||
Reference in new issue
Block a user