| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- /*
- This file is part of Telegram Desktop,
- the official desktop application for the Telegram messaging service.
- For license and copyright information please follow this link:
- https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
- */
- #include "editor/editor_crop.h"
- #include "ui/userpic_view.h"
- #include "styles/style_editor.h"
- #include "styles/style_basic.h"
- #include "styles/style_dialogs.h"
- namespace Editor {
- namespace {
- 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 kEAll = Qt::TopEdge
- | Qt::LeftEdge
- | Qt::BottomEdge
- | Qt::RightEdge;
- std::tuple<int, int, int, int> RectEdges(const QRectF &r) {
- return { r.left(), r.top(), r.left() + r.width(), r.top() + r.height() };
- }
- QPoint PointOfEdge(Qt::Edges e, const QRectF &r) {
- switch(e) {
- case kETL: return QPoint(r.x(), r.y());
- 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());
- default: return QPoint();
- }
- }
- QSizeF FlipSizeByRotation(const QSizeF &size, int angle) {
- return (((angle / 90) % 2) == 1) ? size.transposed() : size;
- }
- [[nodiscard]] QRectF OriginalCrop(QSize outer, QSize inner) {
- const auto size = inner.scaled(outer, Qt::KeepAspectRatio);
- return QRectF(
- (outer.width() - size.width()) / 2,
- (outer.height() - size.height()) / 2,
- size.width(),
- size.height());
- }
- } // namespace
- Crop::Crop(
- not_null<Ui::RpWidget*> parent,
- const PhotoModifications &modifications,
- const QSize &imageSize,
- EditorData data)
- : RpWidget(parent)
- , _pointSize(st::photoEditorCropPointSize)
- , _pointSizeH(_pointSize / 2.)
- , _innerMargins(QMarginsF(_pointSizeH, _pointSizeH, _pointSizeH, _pointSizeH)
- .toMargins())
- , _offset(_innerMargins.left(), _innerMargins.top())
- , _edgePointMargins(_pointSizeH, _pointSizeH, -_pointSizeH, -_pointSizeH)
- , _imageSize(imageSize)
- , _data(std::move(data))
- , _cropOriginal(modifications.crop.isValid()
- ? modifications.crop
- : !_data.exactSize.isEmpty()
- ? OriginalCrop(_imageSize, _data.exactSize)
- : QRectF(QPoint(), _imageSize))
- , _angle(modifications.angle)
- , _flipped(modifications.flipped)
- , _keepAspectRatio(_data.keepAspectRatio) {
- setMouseTracking(true);
- paintRequest(
- ) | rpl::start_with_next([=] {
- auto p = QPainter(this);
- p.fillPath(_painterPath, st::photoCropFadeBg);
- paintPoints(p);
- }, lifetime());
- }
- void Crop::applyTransform(
- const QRect &geometry,
- int angle,
- bool flipped,
- const QSizeF &scaledImageSize) {
- if (geometry.isEmpty()) {
- return;
- }
- setGeometry(geometry);
- _innerRect = QRectF(_offset, FlipSizeByRotation(scaledImageSize, angle));
- _ratio.w = scaledImageSize.width() / float64(_imageSize.width());
- _ratio.h = scaledImageSize.height() / float64(_imageSize.height());
- _flipped = flipped;
- _angle = angle;
- const auto cropHolder = QRectF(QPointF(), scaledImageSize);
- const auto cropHolderCenter = cropHolder.center();
- auto matrix = QTransform()
- .translate(cropHolderCenter.x(), cropHolderCenter.y())
- .scale(flipped ? -1 : 1, 1)
- .rotate(angle)
- .translate(-cropHolderCenter.x(), -cropHolderCenter.y());
- const auto cropHolderRotated = matrix.mapRect(cropHolder);
- auto cropPaint = matrix
- .scale(_ratio.w, _ratio.h)
- .mapRect(_cropOriginal)
- .translated(
- -cropHolderRotated.x() + _offset.x(),
- -cropHolderRotated.y() + _offset.y());
- // Check boundaries.
- const auto min = float64(st::photoEditorCropMinSize);
- if ((cropPaint.width() < min) || (cropPaint.height() < min)) {
- cropPaint.setWidth(std::max(min, cropPaint.width()));
- cropPaint.setHeight(std::max(min, cropPaint.height()));
- const auto p = cropPaint.center().toPoint();
- setCropPaint(std::move(cropPaint));
- computeDownState(p);
- performMove(p);
- clearDownState();
- convertCropPaintToOriginal();
- } else {
- setCropPaint(std::move(cropPaint));
- }
- }
- void Crop::paintPoints(QPainter &p) {
- p.save();
- p.setPen(Qt::NoPen);
- p.setBrush(st::photoCropPointFg);
- for (const auto &r : ranges::views::values(_edges)) {
- p.drawRect(r);
- }
- p.restore();
- }
- void Crop::setCropPaint(QRectF &&rect) {
- _cropPaint = std::move(rect);
- updateEdges();
- _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);
- }
- }
- void Crop::convertCropPaintToOriginal() {
- const auto cropHolder = QTransform()
- .scale(_ratio.w, _ratio.h)
- .mapRect(QRectF(QPointF(), FlipSizeByRotation(_imageSize, _angle)));
- const auto cropHolderCenter = cropHolder.center();
- const auto matrix = QTransform()
- .translate(cropHolderCenter.x(), cropHolderCenter.y())
- .rotate(-_angle)
- .scale((_flipped ? -1 : 1) * 1. / _ratio.w, 1. / _ratio.h)
- .translate(-cropHolderCenter.x(), -cropHolderCenter.y());
- const auto cropHolderRotated = matrix.mapRect(cropHolder);
- _cropOriginal = matrix
- .mapRect(QRectF(_cropPaint).translated(-_offset))
- .translated(
- -cropHolderRotated.x(),
- -cropHolderRotated.y());
- }
- void Crop::updateEdges() {
- const auto &s = _pointSize;
- const auto &m = _edgePointMargins;
- const auto &r = _cropPaint;
- for (const auto &e : { kETL, kETR, kEBL, kEBR }) {
- _edges[e] = QRectF(PointOfEdge(e, r), QSize(s, s)) + m;
- }
- }
- Qt::Edges Crop::mouseState(const QPoint &p) {
- for (const auto &[e, r] : _edges) {
- if (r.contains(p)) {
- return e;
- }
- }
- if (_cropPaint.contains(p)) {
- return kEAll;
- }
- return Qt::Edges();
- }
- void Crop::mousePressEvent(QMouseEvent *e) {
- computeDownState(e->pos());
- }
- void Crop::mouseReleaseEvent(QMouseEvent *e) {
- clearDownState();
- convertCropPaintToOriginal();
- }
- void Crop::computeDownState(const QPoint &p) {
- const auto edge = mouseState(p);
- const auto &inner = _innerRect;
- const auto &crop = _cropPaint;
- const auto &[iLeft, iTop, iRight, iBottom] = RectEdges(inner);
- const auto &[cLeft, cTop, cRight, cBottom] = RectEdges(crop);
- _down = InfoAtDown{
- .rect = crop,
- .edge = edge,
- .point = (p - PointOfEdge(edge, crop)),
- .cropRatio = (_cropOriginal.width() / _cropOriginal.height()),
- .borders = InfoAtDown::Borders{
- .left = iLeft - cLeft,
- .right = iRight - cRight,
- .top = iTop - cTop,
- .bottom = iBottom - cBottom,
- }
- };
- if (_keepAspectRatio && (edge != kEAll)) {
- const auto hasLeft = (edge & Qt::LeftEdge);
- const auto hasTop = (edge & Qt::TopEdge);
- const auto xSign = hasLeft ? -1 : 1;
- const auto ySign = hasTop ? -1 : 1;
- auto &xSide = (hasLeft ? _down.borders.left : _down.borders.right);
- auto &ySide = (hasTop ? _down.borders.top : _down.borders.bottom);
- const auto min = std::abs(std::min(xSign * xSide, ySign * ySide));
- const auto xIsMin = ((xSign * xSide) < (ySign * ySide));
- xSide = xSign * min;
- ySide = ySign * min;
- if (!xIsMin) {
- xSide *= _down.cropRatio;
- } else {
- ySide /= _down.cropRatio;
- }
- }
- }
- void Crop::clearDownState() {
- _down = InfoAtDown();
- }
- void Crop::performCrop(const QPoint &pos) {
- const auto &crop = _down.rect;
- const auto &pressedEdge = _down.edge;
- const auto hasLeft = (pressedEdge & Qt::LeftEdge);
- const auto hasTop = (pressedEdge & Qt::TopEdge);
- const auto hasRight = (pressedEdge & Qt::RightEdge);
- const auto hasBottom = (pressedEdge & Qt::BottomEdge);
- const auto diff = [&] {
- auto diff = pos - PointOfEdge(pressedEdge, crop) - _down.point;
- const auto xFactor = hasLeft ? 1 : -1;
- const auto yFactor = hasTop ? 1 : -1;
- const auto &borders = _down.borders;
- const auto &cropRatio = _down.cropRatio;
- if (_keepAspectRatio) {
- const auto diffSign = xFactor * yFactor;
- diff = (cropRatio != 1.)
- ? QPoint(diff.x(), (1. / cropRatio) * diff.x() * diffSign)
- // For square/circle.
- : ((diff.x() * xFactor) < (diff.y() * yFactor))
- ? QPoint(diff.x(), diff.x() * diffSign)
- : QPoint(diff.y() * diffSign, diff.y());
- }
- const auto &minSize = st::photoEditorCropMinSize;
- const auto xMin = xFactor * int(crop.width() - minSize);
- // const auto xMin = int(xFactor * crop.width()
- // - xFactor * minSize * ((cropRatio > 1.) ? cropRatio : 1.));
- const auto yMin = yFactor * int(crop.height() - minSize);
- // const auto yMin = int(yFactor * crop.height()
- // - yFactor * minSize * ((cropRatio < 1.) ? (1. / cropRatio) : 1.));
- const auto x = std::clamp(
- diff.x(),
- hasLeft ? borders.left : xMin,
- hasLeft ? xMin : borders.right);
- const auto y = std::clamp(
- diff.y(),
- hasTop ? borders.top : yMin,
- hasTop ? yMin : borders.bottom);
- return QPoint(x, y);
- }();
- setCropPaint(crop - QMargins(
- hasLeft ? diff.x() : 0,
- hasTop ? diff.y() : 0,
- hasRight ? -diff.x() : 0,
- hasBottom ? -diff.y() : 0));
- }
- void Crop::performMove(const QPoint &pos) {
- const auto &inner = _down.rect;
- const auto &b = _down.borders;
- const auto diffX = std::clamp(pos.x() - _down.point.x(), b.left, b.right);
- const auto diffY = std::clamp(pos.y() - _down.point.y(), b.top, b.bottom);
- setCropPaint(inner.translated(diffX, diffY));
- }
- void Crop::mouseMoveEvent(QMouseEvent *e) {
- const auto pos = e->pos();
- const auto pressedEdge = _down.edge;
- if (pressedEdge) {
- if (pressedEdge == kEAll) {
- performMove(pos);
- } else if (pressedEdge) {
- performCrop(pos);
- }
- update();
- }
- const auto edge = pressedEdge ? pressedEdge : mouseState(pos);
- const auto cursor = ((edge == kETL) || (edge == kEBR))
- ? style::cur_sizefdiag
- : ((edge == kETR) || (edge == kEBL))
- ? style::cur_sizebdiag
- : (edge == kEAll)
- ? style::cur_sizeall
- : style::cur_default;
- setCursor(cursor);
- }
- style::margins Crop::cropMargins() const {
- return _innerMargins;
- }
- QRect Crop::saveCropRect() {
- const auto savedCrop = _cropOriginal.toRect();
- return (!savedCrop.topLeft().isNull() || (savedCrop.size() != _imageSize))
- ? savedCrop
- : QRect();
- }
- } // namespace Editor
|