Crop : ratio verrouillé, bord à bord, pas de marge
- Redimensionnement verrouille le ratio (coins uniquement) - Cadre le plus grand possible (100% d'un bord, pas de marge) - Déplacement contraint dans les limites de la photo - Même comportement dans l'éditeur et l'écran impression Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -158,7 +158,7 @@ class EditorScreen(QWidget):
|
||||
|
||||
# --- Cadre ratio suggéré ---
|
||||
def _show_ratio_crop(self):
|
||||
"""Affiche un cadre de recadrage au ratio 3:2 en suggestion."""
|
||||
"""Affiche un cadre de recadrage au ratio 3:2, le plus grand possible."""
|
||||
if not self.pixmap_item:
|
||||
return
|
||||
self.is_cropping = True
|
||||
@@ -171,14 +171,17 @@ class EditorScreen(QWidget):
|
||||
if img_w < img_h:
|
||||
ratio = 1.0 / ratio
|
||||
|
||||
# Plus grande zone possible
|
||||
# Le plus grand possible : bord à bord, pas de marge
|
||||
if img_w / img_h > ratio:
|
||||
crop_h = img_h * 0.95
|
||||
# Image plus large → hauteur = 100%, largeur ajustée
|
||||
crop_h = img_h
|
||||
crop_w = crop_h * ratio
|
||||
else:
|
||||
crop_w = img_w * 0.95
|
||||
# Image plus haute → largeur = 100%, hauteur ajustée
|
||||
crop_w = img_w
|
||||
crop_h = crop_w / ratio
|
||||
|
||||
# Centrer
|
||||
x = (img_w - crop_w) / 2
|
||||
y = (img_h - crop_h) / 2
|
||||
|
||||
|
||||
@@ -196,17 +196,15 @@ class PrintScreen(QWidget):
|
||||
# Image portrait → cadre portrait (inverser le ratio)
|
||||
ratio = 1.0 / ratio
|
||||
|
||||
# Calculer la plus grande zone de crop possible dans l'image
|
||||
# Le plus grand possible : bord à bord, pas de marge
|
||||
if img_w / img_h > ratio:
|
||||
# Image plus large que le ratio → hauteur max, largeur ajustée
|
||||
crop_h = img_h * 0.9
|
||||
crop_h = img_h
|
||||
crop_w = crop_h * ratio
|
||||
else:
|
||||
# Image plus haute que le ratio → largeur max, hauteur ajustée
|
||||
crop_w = img_w * 0.9
|
||||
crop_w = img_w
|
||||
crop_h = crop_w / ratio
|
||||
|
||||
# Centrer le cadre
|
||||
# Centrer
|
||||
x = (img_w - crop_w) / 2
|
||||
y = (img_h - crop_h) / 2
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ HANDLE_SIZE = 20
|
||||
|
||||
|
||||
class CropOverlay(QGraphicsRectItem):
|
||||
"""Rectangle de recadrage avec poignées tactiles redimensionnables."""
|
||||
"""Rectangle de recadrage avec ratio verrouillé et poignées tactiles."""
|
||||
|
||||
def __init__(self, rect, parent=None):
|
||||
super().__init__(rect, parent)
|
||||
@@ -19,15 +19,41 @@ class CropOverlay(QGraphicsRectItem):
|
||||
self.setAcceptHoverEvents(True)
|
||||
|
||||
self._resizing = False
|
||||
self._resize_edge = None
|
||||
self._resize_corner = None
|
||||
self._start_rect = None
|
||||
self._start_pos = None
|
||||
self._bounds = None
|
||||
# Ratio verrouillé (largeur / hauteur)
|
||||
self._locked_ratio = rect.width() / rect.height() if rect.height() > 0 else 1.5
|
||||
|
||||
def set_bounds(self, rect):
|
||||
"""Limites dans lesquelles le crop peut bouger (la photo)."""
|
||||
self._bounds = rect
|
||||
|
||||
def set_ratio(self, ratio):
|
||||
"""Change le ratio verrouillé."""
|
||||
self._locked_ratio = ratio
|
||||
|
||||
def itemChange(self, change, value):
|
||||
"""Empêche le cadre de sortir des limites de la photo."""
|
||||
if change == QGraphicsItem.GraphicsItemChange.ItemPositionChange and self._bounds:
|
||||
r = self.rect()
|
||||
new_pos = value
|
||||
b = self._bounds
|
||||
|
||||
# Contraindre le déplacement
|
||||
if new_pos.x() + r.left() < b.left():
|
||||
new_pos.setX(b.left() - r.left())
|
||||
if new_pos.y() + r.top() < b.top():
|
||||
new_pos.setY(b.top() - r.top())
|
||||
if new_pos.x() + r.right() > b.right():
|
||||
new_pos.setX(b.right() - r.right())
|
||||
if new_pos.y() + r.bottom() > b.bottom():
|
||||
new_pos.setY(b.bottom() - r.bottom())
|
||||
|
||||
return new_pos
|
||||
return super().itemChange(change, value)
|
||||
|
||||
def paint(self, painter, option, widget=None):
|
||||
super().paint(painter, option, widget)
|
||||
r = self.rect()
|
||||
@@ -36,7 +62,6 @@ class CropOverlay(QGraphicsRectItem):
|
||||
if self._bounds:
|
||||
painter.setBrush(QBrush(QColor(0, 0, 0, 120)))
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
# Les 4 zones sombres autour du crop
|
||||
b = self.mapRectFromScene(self._bounds)
|
||||
# Haut
|
||||
painter.drawRect(QRectF(b.left(), b.top(), b.width(), r.top() - b.top()))
|
||||
@@ -64,10 +89,10 @@ class CropOverlay(QGraphicsRectItem):
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
pos = event.pos()
|
||||
edge = self._detect_edge(pos)
|
||||
if edge:
|
||||
corner = self._detect_corner(pos)
|
||||
if corner:
|
||||
self._resizing = True
|
||||
self._resize_edge = edge
|
||||
self._resize_corner = corner
|
||||
self._start_rect = QRectF(self.rect())
|
||||
self._start_pos = pos
|
||||
event.accept()
|
||||
@@ -78,20 +103,69 @@ class CropOverlay(QGraphicsRectItem):
|
||||
if self._resizing:
|
||||
delta = event.pos() - self._start_pos
|
||||
r = QRectF(self._start_rect)
|
||||
e = self._resize_edge
|
||||
corner = self._resize_corner
|
||||
|
||||
if "left" in e:
|
||||
r.setLeft(r.left() + delta.x())
|
||||
if "right" in e:
|
||||
r.setRight(r.right() + delta.x())
|
||||
if "top" in e:
|
||||
r.setTop(r.top() + delta.y())
|
||||
if "bottom" in e:
|
||||
r.setBottom(r.bottom() + delta.y())
|
||||
# Redimensionner en gardant le ratio
|
||||
# On utilise le déplacement diagonal le plus grand
|
||||
if "right" in corner:
|
||||
dx = delta.x()
|
||||
else:
|
||||
dx = -delta.x()
|
||||
|
||||
if "bottom" in corner:
|
||||
dy = delta.y()
|
||||
else:
|
||||
dy = -delta.y()
|
||||
|
||||
# Prendre le delta dominant
|
||||
if abs(dx) > abs(dy):
|
||||
new_w = r.width() + dx
|
||||
new_h = new_w / self._locked_ratio
|
||||
else:
|
||||
new_h = r.height() + dy
|
||||
new_w = new_h * self._locked_ratio
|
||||
|
||||
# Taille minimum
|
||||
if r.width() >= 50 and r.height() >= 50:
|
||||
self.setRect(r)
|
||||
if new_w < 50 or new_h < 50:
|
||||
event.accept()
|
||||
return
|
||||
|
||||
# Appliquer selon le coin tiré
|
||||
if corner == "bottomright":
|
||||
r.setWidth(new_w)
|
||||
r.setHeight(new_h)
|
||||
elif corner == "bottomleft":
|
||||
r.setLeft(r.right() - new_w)
|
||||
r.setHeight(new_h)
|
||||
elif corner == "topright":
|
||||
r.setWidth(new_w)
|
||||
r.setTop(r.bottom() - new_h)
|
||||
elif corner == "topleft":
|
||||
r.setLeft(r.right() - new_w)
|
||||
r.setTop(r.bottom() - new_h)
|
||||
|
||||
# Contraindre aux limites de la photo
|
||||
if self._bounds:
|
||||
b = self.mapRectFromScene(self._bounds)
|
||||
# Ne pas dépasser les bords
|
||||
if r.left() < b.left():
|
||||
r.moveLeft(b.left())
|
||||
if r.top() < b.top():
|
||||
r.moveTop(b.top())
|
||||
if r.right() > b.right():
|
||||
over = r.right() - b.right()
|
||||
new_w = r.width() - over
|
||||
new_h = new_w / self._locked_ratio
|
||||
r.setWidth(new_w)
|
||||
r.setHeight(new_h)
|
||||
if r.bottom() > b.bottom():
|
||||
over = r.bottom() - b.bottom()
|
||||
new_h = r.height() - over
|
||||
new_w = new_h * self._locked_ratio
|
||||
r.setWidth(new_w)
|
||||
r.setHeight(new_h)
|
||||
|
||||
self.setRect(r)
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseMoveEvent(event)
|
||||
@@ -99,24 +173,28 @@ class CropOverlay(QGraphicsRectItem):
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self._resizing:
|
||||
self._resizing = False
|
||||
self._resize_edge = None
|
||||
self._resize_corner = None
|
||||
event.accept()
|
||||
else:
|
||||
super().mouseReleaseEvent(event)
|
||||
|
||||
def _detect_edge(self, pos):
|
||||
def _detect_corner(self, pos):
|
||||
r = self.rect()
|
||||
margin = HANDLE_SIZE
|
||||
edges = ""
|
||||
if abs(pos.y() - r.top()) < margin:
|
||||
edges += "top"
|
||||
elif abs(pos.y() - r.bottom()) < margin:
|
||||
edges += "bottom"
|
||||
if abs(pos.x() - r.left()) < margin:
|
||||
edges += "left"
|
||||
elif abs(pos.x() - r.right()) < margin:
|
||||
edges += "right"
|
||||
return edges or None
|
||||
near_top = abs(pos.y() - r.top()) < margin
|
||||
near_bottom = abs(pos.y() - r.bottom()) < margin
|
||||
near_left = abs(pos.x() - r.left()) < margin
|
||||
near_right = abs(pos.x() - r.right()) < margin
|
||||
|
||||
if near_top and near_left:
|
||||
return "topleft"
|
||||
if near_top and near_right:
|
||||
return "topright"
|
||||
if near_bottom and near_left:
|
||||
return "bottomleft"
|
||||
if near_bottom and near_right:
|
||||
return "bottomright"
|
||||
return None
|
||||
|
||||
def get_crop_rect(self):
|
||||
"""Retourne le rectangle de crop en coordonnées scène."""
|
||||
|
||||
Reference in New Issue
Block a user