diff --git a/Telegram/SourceFiles/editor/editor_crop.cpp b/Telegram/SourceFiles/editor/editor_crop.cpp index 9dcf19432..57e324350 100644 --- a/Telegram/SourceFiles/editor/editor_crop.cpp +++ b/Telegram/SourceFiles/editor/editor_crop.cpp @@ -21,11 +21,11 @@ constexpr auto kEAll = Qt::TopEdge | Qt::BottomEdge | Qt::RightEdge; -std::tuple RectEdges(const QRect &r) { +std::tuple RectEdges(const QRectF &r) { return { r.left(), r.top(), r.left() + r.width(), r.top() + r.height() }; } -QPoint PointOfEdge(Qt::Edges e, const QRect &r) { +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()); @@ -35,6 +35,10 @@ QPoint PointOfEdge(Qt::Edges e, const QRect &r) { } } +QSizeF FlipSizeByRotation(const QSizeF &size, int angle) { + return (((angle / 90) % 2) == 1) ? size.transposed() : size; +} + } // namespace Crop::Crop( @@ -47,17 +51,13 @@ Crop::Crop( , _innerMargins(QMarginsF(_pointSizeH, _pointSizeH, _pointSizeH, _pointSizeH) .toMargins()) , _offset(_innerMargins.left(), _innerMargins.top()) -, _edgePointMargins(_pointSizeH, _pointSizeH, -_pointSizeH, -_pointSizeH) { - - _angle = modifications.angle; - _flipped = modifications.flipped; - _cropRect = modifications.crop; - if (_cropRect.isValid()) { - const auto inner = QRect(QPoint(), imageSize); - _innerRect = QRect( - QPoint(), - QMatrix().rotate(-_angle).mapRect(inner).size()); - } +, _edgePointMargins(_pointSizeH, _pointSizeH, -_pointSizeH, -_pointSizeH) +, _imageSize(imageSize) +, _cropOriginal(modifications.crop.isValid() + ? modifications.crop + : QRectF(QPoint(), _imageSize)) +, _angle(modifications.angle) +, _flipped(modifications.flipped) { setMouseTracking(true); @@ -66,63 +66,61 @@ Crop::Crop( Painter p(this); p.fillPath(_painterPath, st::photoCropFadeBg); - paintPoints(p); - }, lifetime()); } -void Crop::applyTransform(QRect geometry, int angle, bool flipped) { +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 nowInner = QRect(QPoint(), geometry.size()) - _innerMargins; - const auto wasInner = _innerRect.isEmpty() ? nowInner : _innerRect; - const auto nowInnerF = QRectF(QPointF(), QSizeF(nowInner.size())); - const auto wasInnerF = QRectF(QPointF(), QSizeF(wasInner.size())); + const auto cropHolder = QRectF(QPointF(), scaledImageSize); + const auto cropHolderCenter = cropHolder.center(); - _innerRect = nowInner; + auto matrix = QMatrix() + .translate(cropHolderCenter.x(), cropHolderCenter.y()) + .scale(flipped ? -1 : 1, 1) + .rotate(angle) + .translate(-cropHolderCenter.x(), -cropHolderCenter.y()); - if (_cropRect.isEmpty()) { - setCropRect(_innerRect.translated(-_offset)); - } + const auto cropHolderRotated = matrix.mapRect(cropHolder); - const auto angleTo = (angle - _angle) * (flipped ? -1 : 1); - const auto flippedChanges = (_flipped != flipped); + auto cropPaint = matrix + .scale(_ratio.w, _ratio.h) + .mapRect(_cropOriginal) + .translated( + -cropHolderRotated.x() + _offset.x(), + -cropHolderRotated.y() + _offset.y()); - const auto nowInnerCenter = nowInnerF.center(); - const auto nowInnerRotated = QMatrix() - .translate(nowInnerCenter.x(), nowInnerCenter.y()) - .rotate(-angleTo) - .translate(-nowInnerCenter.x(), -nowInnerCenter.y()) - .mapRect(nowInnerF); + // Check boundaries. + const auto min = float64(st::cropMinSize); + if ((cropPaint.width() < min) || (cropPaint.height() < min)) { + cropPaint.setWidth(std::max(min, cropPaint.width())); + cropPaint.setHeight(std::max(min, cropPaint.height())); - const auto nowCropRect = resizedCropRect(wasInnerF, nowInnerRotated) - .translated(nowInnerRotated.topLeft()); + const auto p = cropPaint.center().toPoint(); + setCropPaint(std::move(cropPaint)); - const auto nowInnerRotatedCenter = nowInnerRotated.center(); - - setCropRect(QMatrix() - .translate(nowInnerRotatedCenter.x(), nowInnerRotatedCenter.y()) - .rotate(angleTo) - .scale(flippedChanges ? -1 : 1, 1) - .translate(-nowInnerRotatedCenter.x(), -nowInnerRotatedCenter.y()) - .mapRect(nowCropRect) - .toRect()); - - { - // Check boundaries. - const auto p = _cropRectPaint.center(); computeDownState(p); performMove(p); clearDownState(); - } - _flipped = flipped; - _angle = angle; + convertCropPaintToOriginal(); + } else { + setCropPaint(std::move(cropPaint)); + } } void Crop::paintPoints(Painter &p) { @@ -135,25 +133,41 @@ void Crop::paintPoints(Painter &p) { p.restore(); } -void Crop::setCropRect(QRect &&rect) { - _cropRect = std::move(rect); - _cropRectPaint = _cropRect.translated(_offset); +void Crop::setCropPaint(QRectF &&rect) { + _cropPaint = std::move(rect); + updateEdges(); _painterPath.clear(); _painterPath.addRect(_innerRect); - _painterPath.addRect(_cropRectPaint); + _painterPath.addRect(_cropPaint); } -void Crop::setCropRectPaint(QRect &&rect) { - rect.translate(-_offset); - setCropRect(std::move(rect)); +void Crop::convertCropPaintToOriginal() { + const auto cropHolder = QMatrix() + .scale(_ratio.w, _ratio.h) + .mapRect(QRectF(QPointF(), FlipSizeByRotation(_imageSize, _angle))); + const auto cropHolderCenter = cropHolder.center(); + + const auto matrix = QMatrix() + .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 = _cropRectPaint; + const auto &r = _cropPaint; for (const auto &e : { kETL, kETR, kEBL, kEBR }) { _edges[e] = QRectF(PointOfEdge(e, r), QSize(s, s)) + m; } @@ -165,7 +179,7 @@ Qt::Edges Crop::mouseState(const QPoint &p) { return e; } } - if (_cropRectPaint.contains(p)) { + if (_cropPaint.contains(p)) { return kEAll; } return Qt::Edges(); @@ -177,12 +191,13 @@ void Crop::mousePressEvent(QMouseEvent *e) { 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 = _cropRectPaint; + const auto &crop = _cropPaint; const auto [iLeft, iTop, iRight, iBottom] = RectEdges(inner); const auto [cLeft, cTop, cRight, cBottom] = RectEdges(crop); _down = InfoAtDown{ @@ -215,8 +230,10 @@ void Crop::performCrop(const QPoint &pos) { const auto vFactor = hasTop ? 1 : -1; const auto &borders = _down.borders; - const auto hMin = hFactor * crop.width() - hFactor * st::cropMinSize; - const auto vMin = vFactor * crop.height() - vFactor * st::cropMinSize; + const auto hMin = int( + hFactor * crop.width() - hFactor * st::cropMinSize); + const auto vMin = int( + vFactor * crop.height() - vFactor * st::cropMinSize); const auto x = std::clamp( diff.x(), @@ -232,7 +249,7 @@ void Crop::performCrop(const QPoint &pos) { } return QPoint(x, y); }(); - setCropRectPaint(crop - QMargins( + setCropPaint(crop - QMargins( hasLeft ? diff.x() : 0, hasTop ? diff.y() : 0, hasRight ? -diff.x() : 0, @@ -244,7 +261,7 @@ void Crop::performMove(const QPoint &pos) { 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); - setCropRectPaint(inner.translated(diffX, diffY)); + setCropPaint(inner.translated(diffX, diffY)); } void Crop::mouseMoveEvent(QMouseEvent *e) { @@ -272,29 +289,12 @@ void Crop::mouseMoveEvent(QMouseEvent *e) { setCursor(cursor); } -QRect Crop::innerRect() const { - return _innerRect; -} - style::margins Crop::cropMargins() const { return _innerMargins; } -QRect Crop::saveCropRect(const QRect &from, const QRect &to) { - return resizedCropRect(QRectF(from), QRectF(to)).toRect(); -} - -QRectF Crop::resizedCropRect(const QRectF &from, const QRectF &to) { - const auto ratioW = to.width() / float64(from.width()); - const auto ratioH = to.height() / float64(from.height()); - const auto &min = float64(st::cropMinSize); - const auto &r = _cropRect; - - return QRectF( - r.x() * ratioW, - r.y() * ratioH, - std::max(r.width() * ratioW, min), - std::max(r.height() * ratioH, min)); +QRect Crop::saveCropRect() { + return _cropOriginal.toRect(); } } // namespace Editor diff --git a/Telegram/SourceFiles/editor/editor_crop.h b/Telegram/SourceFiles/editor/editor_crop.h index d4e19719f..f0290f6f2 100644 --- a/Telegram/SourceFiles/editor/editor_crop.h +++ b/Telegram/SourceFiles/editor/editor_crop.h @@ -22,11 +22,12 @@ public: const PhotoModifications &modifications, const QSize &imageSize); - void applyTransform(QRect geometry, int angle, bool flipped); - [[nodiscard]] QRect innerRect() const; - [[nodiscard]] QRect saveCropRect( - const QRect &from, - const QRect &to); + void applyTransform( + const QRect &geometry, + int angle, + bool flipped, + const QSizeF &scaledImageSize); + [[nodiscard]] QRect saveCropRect(); [[nodiscard]] style::margins cropMargins() const; protected: @@ -36,7 +37,7 @@ protected: private: struct InfoAtDown { - QRect rect; + QRectF rect; Qt::Edges edge = 0; QPoint point; @@ -48,17 +49,12 @@ private: } borders; }; - [[nodiscard]] QRectF resizedCropRect( - const QRectF &from, - const QRectF &to); - void paintPoints(Painter &p); void updateEdges(); [[nodiscard]] QPoint pointOfEdge(Qt::Edges e) const; - void setCropRect(QRect &&rect); - void setCropRectPaint(QRect &&rect); - void rotate(bool clockwise = true); + void setCropPaint(QRectF &&rect); + void convertCropPaintToOriginal(); void computeDownState(const QPoint &p); void clearDownState(); @@ -71,15 +67,18 @@ private: const style::margins _innerMargins; const QPoint _offset; const QMarginsF _edgePointMargins; + const QSize _imageSize; base::flat_map _edges; - // Is translated with the inner indentation. - QRect _cropRectPaint; - // Is not. - QRect _cropRect; + struct { + float64 w = 0.; + float64 h = 0.; + } _ratio; - QRect _innerRect; + QRectF _cropPaint; + QRectF _cropOriginal; + QRectF _innerRect; QPainterPath _painterPath; diff --git a/Telegram/SourceFiles/editor/photo_editor_common.cpp b/Telegram/SourceFiles/editor/photo_editor_common.cpp index 981d224a9..cdc9278d5 100644 --- a/Telegram/SourceFiles/editor/photo_editor_common.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_common.cpp @@ -19,6 +19,9 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) { mods.paint->render(&p, image.rect()); } + auto cropped = mods.crop.isValid() + ? image.copy(mods.crop) + : image; QTransform transform; if (mods.flipped) { transform.scale(-1, 1); @@ -26,11 +29,7 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) { if (mods.angle) { transform.rotate(mods.angle); } - auto newImage = image.transformed(transform); - if (mods.crop.isValid()) { - newImage = newImage.copy(mods.crop); - } - return newImage; + return cropped.transformed(transform); } bool PhotoModifications::empty() const { diff --git a/Telegram/SourceFiles/editor/photo_editor_content.cpp b/Telegram/SourceFiles/editor/photo_editor_content.cpp index 99758345b..42362e2f1 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_content.cpp @@ -39,13 +39,13 @@ PhotoEditorContent::PhotoEditorContent( if (size.isEmpty()) { return; } - const auto imageSize = [&] { + const auto imageSizeF = [&] { const auto rotatedSize = FlipSizeByRotation(size, mods.angle); const auto m = _crop->cropMargins(); const auto sizeForCrop = rotatedSize - QSize(m.left() + m.right(), m.top() + m.bottom()); - const auto originalSize = photo->size(); + const auto originalSize = QSizeF(photo->size()); if ((originalSize.width() > sizeForCrop.width()) || (originalSize.height() > sizeForCrop.height())) { return originalSize.scaled( @@ -54,6 +54,7 @@ PhotoEditorContent::PhotoEditorContent( } return originalSize; }(); + const auto imageSize = QSize(imageSizeF.width(), imageSizeF.height()); _imageRect = QRect( QPoint(-imageSize.width() / 2, -imageSize.height() / 2), imageSize); @@ -69,7 +70,7 @@ PhotoEditorContent::PhotoEditorContent( _crop->applyTransform( geometry + _crop->cropMargins(), mods.angle, - mods.flipped); + mods.flipped, imageSizeF); _paint->applyTransform(geometry, mods.angle, mods.flipped); }, lifetime()); @@ -92,7 +93,7 @@ void PhotoEditorContent::applyModifications( } void PhotoEditorContent::save(PhotoModifications &modifications) { - modifications.crop = _crop->saveCropRect(_imageRect, _photo->rect()); + modifications.crop = _crop->saveCropRect(); _paint->keepResult(); if (!modifications.paint) { modifications.paint = _paint->saveScene();