[img-editor] Improved crop overlay with 3x3 grid.

This commit is contained in:
23rd
2026-03-16 13:40:12 +03:00
parent 776260d265
commit f1117b0071
2 changed files with 181 additions and 19 deletions
+174 -18
View File
@@ -19,6 +19,10 @@ constexpr auto kETL = Qt::TopEdge | Qt::LeftEdge;
constexpr auto kETR = Qt::TopEdge | Qt::RightEdge;
constexpr auto kEBL = Qt::BottomEdge | Qt::LeftEdge;
constexpr auto kEBR = Qt::BottomEdge | Qt::RightEdge;
constexpr auto kEL = Qt::Edges(Qt::LeftEdge);
constexpr auto kER = Qt::Edges(Qt::RightEdge);
constexpr auto kET = Qt::Edges(Qt::TopEdge);
constexpr auto kEB = Qt::Edges(Qt::BottomEdge);
constexpr auto kEAll = Qt::TopEdge
| Qt::LeftEdge
| Qt::BottomEdge
@@ -34,6 +38,10 @@ QPoint PointOfEdge(Qt::Edges e, const QRectF &r) {
case kETR: return QPoint(r.x() + r.width(), r.y());
case kEBL: return QPoint(r.x(), r.y() + r.height());
case kEBR: return QPoint(r.x() + r.width(), r.y() + r.height());
case kEL: return QPoint(r.x(), r.y() + (r.height() / 2.));
case kER: return QPoint(r.x() + r.width(), r.y() + (r.height() / 2.));
case kET: return QPoint(r.x() + (r.width() / 2.), r.y());
case kEB: return QPoint(r.x() + (r.width() / 2.), r.y() + r.height());
default: return QPoint();
}
}
@@ -83,7 +91,12 @@ Crop::Crop(
auto p = QPainter(this);
p.fillPath(_painterPath, st::photoCropFadeBg);
paintPoints(p);
paintFrame(p);
const auto gridOpacity = _gridOpacityAnimation.value(
_gridVisible ? 1. : 0.);
if (gridOpacity > 0.) {
paintGrid(p, gridOpacity);
}
}, lifetime());
}
@@ -140,12 +153,103 @@ void Crop::applyTransform(
}
}
void Crop::paintPoints(QPainter &p) {
QPainterPath Crop::cropPath() const {
auto result = QPainterPath();
if (_data.cropType == EditorData::CropType::Ellipse) {
result.addEllipse(_cropPaint);
} else if (_data.cropType == EditorData::CropType::RoundedRect) {
const auto radius = std::min(_cropPaint.width(), _cropPaint.height())
* Ui::ForumUserpicRadiusMultiplier();
result.addRoundedRect(_cropPaint, radius, radius);
} else {
result.addRect(_cropPaint);
}
return result;
}
void Crop::paintFrame(QPainter &p) {
const auto framePath = cropPath();
auto frameStroker = QPainterPathStroker();
frameStroker.setWidth(st::lineWidth * 2);
frameStroker.setJoinStyle(Qt::MiterJoin);
frameStroker.setCapStyle(Qt::SquareCap);
auto frameShape = frameStroker.createStroke(framePath);
p.save();
p.setPen(Qt::NoPen);
p.setBrush(st::photoCropPointFg);
for (const auto &r : ranges::views::values(_edges)) {
p.drawRect(r);
p.setRenderHint(QPainter::Antialiasing, true);
p.fillPath(frameShape, st::photoCropPointFg);
if (_data.cropType == EditorData::CropType::Rect) {
const auto cornerLength = std::min(
float64(st::photoEditorCropPointSize * 2),
std::min(_cropPaint.width(), _cropPaint.height()) / 2.);
if (cornerLength > 0.) {
auto cornerLines = QPainterPath();
cornerLines.moveTo(_cropPaint.left(), _cropPaint.top());
cornerLines.lineTo(
_cropPaint.left() + cornerLength,
_cropPaint.top());
cornerLines.moveTo(_cropPaint.left(), _cropPaint.top());
cornerLines.lineTo(
_cropPaint.left(),
_cropPaint.top() + cornerLength);
cornerLines.moveTo(_cropPaint.right(), _cropPaint.top());
cornerLines.lineTo(
_cropPaint.right() - cornerLength,
_cropPaint.top());
cornerLines.moveTo(_cropPaint.right(), _cropPaint.top());
cornerLines.lineTo(
_cropPaint.right(),
_cropPaint.top() + cornerLength);
cornerLines.moveTo(_cropPaint.left(), _cropPaint.bottom());
cornerLines.lineTo(
_cropPaint.left() + cornerLength,
_cropPaint.bottom());
cornerLines.moveTo(_cropPaint.left(), _cropPaint.bottom());
cornerLines.lineTo(
_cropPaint.left(),
_cropPaint.bottom() - cornerLength);
cornerLines.moveTo(_cropPaint.right(), _cropPaint.bottom());
cornerLines.lineTo(
_cropPaint.right() - cornerLength,
_cropPaint.bottom());
cornerLines.moveTo(_cropPaint.right(), _cropPaint.bottom());
cornerLines.lineTo(
_cropPaint.right(),
_cropPaint.bottom() - cornerLength);
auto cornerStroker = QPainterPathStroker();
cornerStroker.setWidth(st::lineWidth * 4);
cornerStroker.setJoinStyle(Qt::MiterJoin);
cornerStroker.setCapStyle(Qt::SquareCap);
auto cornerColor = st::photoCropPointFg->c;
cornerColor.setAlpha(255);
p.fillPath(cornerStroker.createStroke(cornerLines), cornerColor);
}
}
p.restore();
}
void Crop::paintGrid(QPainter &p, float64 opacity) {
if ((_cropPaint.width() <= 0.) || (_cropPaint.height() <= 0.)) {
return;
}
p.save();
p.setOpacity(opacity);
p.setRenderHint(QPainter::Antialiasing, true);
p.setClipPath(cropPath());
auto pen = QPen(st::photoCropPointFg, st::lineWidth);
pen.setCapStyle(Qt::FlatCap);
p.setPen(pen);
const auto horizontalStep = _cropPaint.width() / 3.;
const auto verticalStep = _cropPaint.height() / 3.;
for (auto i = 1; i < 3; ++i) {
const auto x = _cropPaint.left() + (horizontalStep * i);
const auto y = _cropPaint.top() + (verticalStep * i);
p.drawLine(
QPointF(x, _cropPaint.top()),
QPointF(x, _cropPaint.bottom()));
p.drawLine(
QPointF(_cropPaint.left(), y),
QPointF(_cropPaint.right(), y));
}
p.restore();
}
@@ -157,15 +261,7 @@ void Crop::setCropPaint(QRectF &&rect) {
_painterPath.clear();
_painterPath.addRect(_innerRect);
if (_data.cropType == EditorData::CropType::Ellipse) {
_painterPath.addEllipse(_cropPaint);
} else if (_data.cropType == EditorData::CropType::RoundedRect) {
const auto radius = std::min(_cropPaint.width(), _cropPaint.height())
* Ui::ForumUserpicRadiusMultiplier();
_painterPath.addRoundedRect(_cropPaint, radius, radius);
} else {
_painterPath.addRect(_cropPaint);
}
_painterPath.addPath(cropPath());
}
void Crop::convertCropPaintToOriginal() {
@@ -193,15 +289,46 @@ void Crop::updateEdges() {
const auto &s = _pointSize;
const auto &m = _edgePointMargins;
const auto &r = _cropPaint;
const auto sideWidth = std::max(0., r.width() - s);
const auto sideHeight = std::max(0., r.height() - s);
for (const auto &e : { kETL, kETR, kEBL, kEBR }) {
_edges[e] = QRectF(PointOfEdge(e, r), QSize(s, s)) + m;
}
if (!_keepAspectRatio) {
_edges[kEL] = QRectF(
r.left() - _pointSizeH,
r.top() + _pointSizeH,
s,
sideHeight);
_edges[kER] = QRectF(
r.right() - _pointSizeH,
r.top() + _pointSizeH,
s,
sideHeight);
_edges[kET] = QRectF(
r.left() + _pointSizeH,
r.top() - _pointSizeH,
sideWidth,
s);
_edges[kEB] = QRectF(
r.left() + _pointSizeH,
r.bottom() - _pointSizeH,
sideWidth,
s);
} else {
_edges.erase(kEL);
_edges.erase(kER);
_edges.erase(kET);
_edges.erase(kEB);
}
}
Qt::Edges Crop::mouseState(const QPoint &p) {
for (const auto &[e, r] : _edges) {
if (r.contains(p)) {
return e;
for (const auto &e : { kETL, kETR, kEBL, kEBR, kEL, kER, kET, kEB }) {
if (const auto i = _edges.find(e); i != end(_edges)) {
if (i->second.contains(p)) {
return e;
}
}
}
if (_cropPaint.contains(p)) {
@@ -212,9 +339,16 @@ Qt::Edges Crop::mouseState(const QPoint &p) {
void Crop::mousePressEvent(QMouseEvent *e) {
computeDownState(e->pos());
if (_down.edge) {
setGridVisible(true, false);
}
}
void Crop::mouseReleaseEvent(QMouseEvent *e) {
const auto hadEdge = bool(_down.edge);
if (hadEdge) {
setGridVisible(false, true);
}
clearDownState();
convertCropPaintToOriginal();
}
@@ -263,6 +397,24 @@ void Crop::clearDownState() {
_down = InfoAtDown();
}
void Crop::setGridVisible(bool visible, bool animated) {
if ((_gridVisible == visible) && !_gridOpacityAnimation.animating()) {
return;
}
const auto from = _gridOpacityAnimation.value(_gridVisible ? 1. : 0.);
const auto to = visible ? 1. : 0.;
_gridVisible = visible;
_gridOpacityAnimation.stop();
if (animated) {
_gridOpacityAnimation.start(
[=] { update(); },
from,
to,
st::photoEditorBarAnimationDuration * std::abs(to - from));
}
update();
}
void Crop::performCrop(const QPoint &pos) {
const auto &crop = _down.rect;
const auto &pressedEdge = _down.edge;
@@ -338,6 +490,10 @@ void Crop::mouseMoveEvent(QMouseEvent *e) {
? style::cur_sizefdiag
: ((edge == kETR) || (edge == kEBL))
? style::cur_sizebdiag
: ((edge == kEL) || (edge == kER))
? style::cur_sizehor
: ((edge == kET) || (edge == kEB))
? style::cur_sizever
: (edge == kEAll)
? style::cur_sizeall
: style::cur_default;
+7 -1
View File
@@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#pragma once
#include "ui/rp_widget.h"
#include "ui/effects/animations.h"
#include "base/flat_map.h"
#include "editor/photo_editor_common.h"
@@ -51,7 +52,10 @@ private:
} borders;
};
void paintPoints(QPainter &p);
void paintFrame(QPainter &p);
void paintGrid(QPainter &p, float64 opacity);
void setGridVisible(bool visible, bool animated);
[[nodiscard]] QPainterPath cropPath() const;
void updateEdges();
[[nodiscard]] QPoint pointOfEdge(Qt::Edges e) const;
@@ -86,9 +90,11 @@ private:
QPainterPath _painterPath;
InfoAtDown _down;
Ui::Animations::Simple _gridOpacityAnimation;
int _angle = 0;
bool _flipped = false;
bool _gridVisible = false;
bool _keepAspectRatio = false;