diff --git a/Telegram/Resources/icons/photo_editor/ratio.svg b/Telegram/Resources/icons/photo_editor/ratio.svg
new file mode 100644
index 0000000000..357b6dd6ef
--- /dev/null
+++ b/Telegram/Resources/icons/photo_editor/ratio.svg
@@ -0,0 +1,7 @@
+
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index 96d6db3340..c4f3ed3368 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -7151,6 +7151,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_photo_editor_menu_flip" = "Flip";
"lng_photo_editor_menu_duplicate" = "Duplicate";
+"lng_photo_editor_crop_original" = "Original";
+"lng_photo_editor_crop_square" = "Square";
+"lng_photo_editor_crop_free" = "Free";
+
"lng_voice_speed_slow" = "Slow";
"lng_voice_speed_normal" = "Normal";
"lng_voice_speed_medium" = "Medium";
diff --git a/Telegram/SourceFiles/editor/editor.style b/Telegram/SourceFiles/editor/editor.style
index dfc4c77247..a8c9157c5f 100644
--- a/Telegram/SourceFiles/editor/editor.style
+++ b/Telegram/SourceFiles/editor/editor.style
@@ -89,6 +89,18 @@ photoEditorStickersButton: IconButton(photoEditorRotateButton) {
}
photoEditorStickersIconActive: icon {{ "settings/settings_stickers", photoEditorButtonIconFgActive }};
+photoEditorCropRatioButton: IconButton(photoEditorRotateButton) {
+ icon: icon {{ "photo_editor/ratio-23x23", photoEditorButtonIconFg, point(0px, 1px) }};
+ iconOver: icon {{ "photo_editor/ratio-23x23", photoEditorButtonIconFgOver, point(0px, 1px) }};
+}
+photoEditorCropRatioIconActive: icon {{ "photo_editor/ratio-23x23", photoEditorButtonIconFgActive, point(0px, 1px) }};
+
+photoEditorCropRatioMenu: PopupMenu(popupMenuWithIcons) {
+ menu: Menu(menuWithIcons) {
+ widthMin: 108px;
+ }
+}
+
photoEditorUndoButtonInactive: icon {{ "photo_editor/undo", photoEditorButtonIconFgInactive }};
photoEditorRedoButtonInactive: icon {{ "photo_editor/undo-flip_horizontal", photoEditorButtonIconFgInactive }};
diff --git a/Telegram/SourceFiles/editor/editor_crop.cpp b/Telegram/SourceFiles/editor/editor_crop.cpp
index 4c1932542e..0fd553768f 100644
--- a/Telegram/SourceFiles/editor/editor_crop.cpp
+++ b/Telegram/SourceFiles/editor/editor_crop.cpp
@@ -177,7 +177,7 @@ void Crop::paintFrame(QPainter &p) {
p.save();
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.);
@@ -439,12 +439,14 @@ void Crop::performCrop(const QPoint &pos) {
}
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 minW = (_keepAspectRatio && cropRatio > 1.)
+ ? (minSize * cropRatio)
+ : float64(minSize);
+ const auto minH = (_keepAspectRatio && cropRatio < 1.)
+ ? (minSize / cropRatio)
+ : float64(minSize);
+ const auto xMin = xFactor * int(crop.width() - minW);
+ const auto yMin = yFactor * int(crop.height() - minH);
const auto x = std::clamp(
diff.x(),
@@ -504,6 +506,48 @@ style::margins Crop::cropMargins() const {
return _innerMargins;
}
+void Crop::setAspectRatio(float64 ratio) {
+ const auto free = (ratio <= 0.);
+ _keepAspectRatio = !free;
+
+ if (!free) {
+ const auto maxW = _innerRect.width();
+ const auto maxH = _innerRect.height();
+ auto newW = maxW;
+ auto newH = maxW / ratio;
+ if (newH > maxH) {
+ newH = maxH;
+ newW = maxH * ratio;
+ }
+
+ const auto center = _cropPaint.center();
+ auto adjusted = QRectF(
+ center.x() - newW / 2.,
+ center.y() - newH / 2.,
+ newW,
+ newH);
+
+ if (adjusted.left() < _innerRect.left()) {
+ adjusted.moveLeft(_innerRect.left());
+ }
+ if (adjusted.top() < _innerRect.top()) {
+ adjusted.moveTop(_innerRect.top());
+ }
+ if (adjusted.right() > _innerRect.right()) {
+ adjusted.moveRight(_innerRect.right());
+ }
+ if (adjusted.bottom() > _innerRect.bottom()) {
+ adjusted.moveBottom(_innerRect.bottom());
+ }
+
+ setCropPaint(std::move(adjusted));
+ convertCropPaintToOriginal();
+ } else {
+ updateEdges();
+ }
+ update();
+}
+
QRect Crop::saveCropRect() {
const auto savedCrop = _cropOriginal.toRect();
return (!savedCrop.topLeft().isNull() || (savedCrop.size() != _imageSize))
diff --git a/Telegram/SourceFiles/editor/editor_crop.h b/Telegram/SourceFiles/editor/editor_crop.h
index 8f73d0d6f4..b9d7eec655 100644
--- a/Telegram/SourceFiles/editor/editor_crop.h
+++ b/Telegram/SourceFiles/editor/editor_crop.h
@@ -31,6 +31,7 @@ public:
const QSizeF &scaledImageSize);
[[nodiscard]] QRect saveCropRect();
[[nodiscard]] style::margins cropMargins() const;
+ void setAspectRatio(float64 ratio);
protected:
void mousePressEvent(QMouseEvent *e) override;
diff --git a/Telegram/SourceFiles/editor/photo_editor.cpp b/Telegram/SourceFiles/editor/photo_editor.cpp
index 70cfeed815..7d722a93c5 100644
--- a/Telegram/SourceFiles/editor/photo_editor.cpp
+++ b/Telegram/SourceFiles/editor/photo_editor.cpp
@@ -224,7 +224,8 @@ PhotoEditor::PhotoEditor(
this,
_controllers,
_modifications,
- data))
+ data,
+ photo->size()))
, _brushes(Deserialize(Core::App().settings().photoEditorBrush()).brushes)
, _brushTool(Deserialize(Core::App().settings().photoEditorBrush()).tool)
, _colorPicker(std::make_unique(
@@ -285,6 +286,10 @@ PhotoEditor::PhotoEditor(
_content->applyModifications(_modifications);
}, lifetime());
+ _controls->aspectRatioChanges() | rpl::on_next([=](float64 ratio) {
+ _content->applyAspectRatio(ratio);
+ }, lifetime());
+
_controls->paintModeRequests(
) | rpl::on_next([=] {
_mode = PhotoEditorMode{
diff --git a/Telegram/SourceFiles/editor/photo_editor_content.cpp b/Telegram/SourceFiles/editor/photo_editor_content.cpp
index 0e1b1c8063..8b1697d613 100644
--- a/Telegram/SourceFiles/editor/photo_editor_content.cpp
+++ b/Telegram/SourceFiles/editor/photo_editor_content.cpp
@@ -142,6 +142,10 @@ void PhotoEditorContent::applyMode(const PhotoEditorMode &mode) {
update();
}
+void PhotoEditorContent::applyAspectRatio(float64 ratio) {
+ _crop->setAspectRatio(ratio);
+}
+
void PhotoEditorContent::applyBrush(const Brush &brush) {
_paint->applyBrush(brush);
}
diff --git a/Telegram/SourceFiles/editor/photo_editor_content.h b/Telegram/SourceFiles/editor/photo_editor_content.h
index 027e5574a6..cf1cc3226f 100644
--- a/Telegram/SourceFiles/editor/photo_editor_content.h
+++ b/Telegram/SourceFiles/editor/photo_editor_content.h
@@ -31,6 +31,7 @@ public:
void applyModifications(PhotoModifications modifications);
void applyMode(const PhotoEditorMode &mode);
void applyBrush(const Brush &brush);
+ void applyAspectRatio(float64 ratio);
void save(PhotoModifications &modifications);
bool handleKeyPress(not_null e) const;
diff --git a/Telegram/SourceFiles/editor/photo_editor_controls.cpp b/Telegram/SourceFiles/editor/photo_editor_controls.cpp
index b4c8621ac6..6a824f7a9c 100644
--- a/Telegram/SourceFiles/editor/photo_editor_controls.cpp
+++ b/Telegram/SourceFiles/editor/photo_editor_controls.cpp
@@ -12,10 +12,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/image/image_prepare.h"
#include "ui/widgets/buttons.h"
#include "ui/widgets/labels.h"
+#include "ui/widgets/popup_menu.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/painter.h"
#include "ui/rect.h"
#include "styles/style_editor.h"
+#include "styles/style_media_player.h" // mediaPlayerMenuCheck
#include
@@ -204,8 +206,10 @@ PhotoEditorControls::PhotoEditorControls(
not_null parent,
std::shared_ptr controllers,
const PhotoModifications modifications,
- const EditorData &data)
+ const EditorData &data,
+ const QSize &imageSize)
: RpWidget(parent)
+, _imageSize(imageSize)
, _bg(st::roundedBg)
, _buttonHeight(st::photoEditorButtonBarHeight)
, _transformButtons(base::make_unique_q(this, _bg))
@@ -235,6 +239,11 @@ PhotoEditorControls::PhotoEditorControls(
, _paintModeButton(base::make_unique_q(
_transformButtons,
st::photoEditorPaintModeButton))
+, _cropRatioButton(data.keepAspectRatio
+ ? nullptr
+ : base::make_unique_q(
+ _transformButtons,
+ st::photoEditorCropRatioButton))
, _transformDone(base::make_unique_q(
_transformButtons,
(data.confirm.isEmpty() ? tr::lng_box_done(tr::now) : data.confirm),
@@ -430,6 +439,50 @@ PhotoEditorControls::PhotoEditorControls(
_flipButton->setIconOverride(icon, icon);
}, _flipButton->lifetime());
+ if (_cropRatioButton) {
+ const auto imageRatio = float64(
+ _imageSize.width()) / _imageSize.height();
+ const auto ratiosMatch = [](float64 a, float64 b) {
+ return std::abs(a - b) < 0.01;
+ };
+ _cropRatioButton->setClickedCallback([=] {
+ _ratioMenu = base::make_unique_q(
+ _cropRatioButton.get(),
+ st::photoEditorCropRatioMenu);
+ _ratioMenu->setForcedOrigin(
+ Ui::PanelAnimation::Origin::BottomRight);
+ const auto check = &st::mediaPlayerMenuCheck;
+ const auto add = [&](const QString &text, float64 ratio) {
+ const auto selected = ratiosMatch(_currentRatio, ratio);
+ _ratioMenu->addAction(
+ text,
+ [=] {
+ if (ratiosMatch(_currentRatio, ratio)) {
+ return;
+ }
+ _currentRatio = ratio;
+ _aspectRatioChanges.fire_copy(ratio);
+ const auto locked = (ratio > 0.);
+ const auto icon = locked
+ ? &st::photoEditorCropRatioIconActive
+ : nullptr;
+ _cropRatioButton->setIconOverride(icon, icon);
+ },
+ selected ? check : nullptr);
+ };
+ add(tr::lng_photo_editor_crop_original(tr::now), imageRatio);
+ add(tr::lng_photo_editor_crop_square(tr::now), 1.);
+ add(u"3:2"_q, 3. / 2.);
+ add(u"16:9"_q, 16. / 9.);
+ add(u"9:16"_q, 9. / 16.);
+ add(tr::lng_photo_editor_crop_free(tr::now), 0.);
+ const auto button = _cropRatioButton.get();
+ const auto bottomRight = button->mapToGlobal(
+ QPoint(button->width(), 0));
+ _ratioMenu->popup(bottomRight);
+ });
+ }
+
updateInputMask();
}
@@ -470,6 +523,10 @@ rpl::producer<> PhotoEditorControls::cancelRequests() const {
}) | rpl::to_empty);
}
+rpl::producer PhotoEditorControls::aspectRatioChanges() const {
+ return _aspectRatioChanges.events();
+}
+
int PhotoEditorControls::bottomButtonsTop() const {
return height()
- st::photoEditorControlsBottomSkip
diff --git a/Telegram/SourceFiles/editor/photo_editor_controls.h b/Telegram/SourceFiles/editor/photo_editor_controls.h
index 920902f4a5..24d95fa799 100644
--- a/Telegram/SourceFiles/editor/photo_editor_controls.h
+++ b/Telegram/SourceFiles/editor/photo_editor_controls.h
@@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "ui/rp_widget.h"
+#include "base/unique_qptr.h"
#include "ui/effects/animations.h"
#include "editor/photo_editor_common.h"
#include "editor/photo_editor_inner_common.h"
@@ -16,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Ui {
class IconButton;
class FlatLabel;
+class PopupMenu;
template
class FadeWrap;
} // namespace Ui
@@ -33,7 +35,8 @@ public:
not_null parent,
std::shared_ptr controllers,
const PhotoModifications modifications,
- const EditorData &data);
+ const EditorData &data,
+ const QSize &imageSize);
[[nodiscard]] rpl::producer rotateRequests() const;
[[nodiscard]] rpl::producer<> flipRequests() const;
@@ -42,6 +45,7 @@ public:
[[nodiscard]] rpl::producer<> cancelRequests() const;
[[nodiscard]] rpl::producer colorLinePositionValue() const;
[[nodiscard]] rpl::producer colorLineShownValue() const;
+ [[nodiscard]] rpl::producer aspectRatioChanges() const;
[[nodiscard]] bool animating() const;
@@ -57,6 +61,7 @@ private:
int bottomButtonsTop() const;
+ const QSize _imageSize;
const style::color &_bg;
const int _buttonHeight;
const base::unique_qptr _transformButtons;
@@ -69,6 +74,7 @@ private:
const base::unique_qptr _flipButton;
const base::unique_qptr _rotateButton;
const base::unique_qptr _paintModeButton;
+ const base::unique_qptr _cropRatioButton;
const base::unique_qptr _transformDone;
const base::unique_qptr _paintCancel;
@@ -78,12 +84,16 @@ private:
const base::unique_qptr _stickersButton;
const base::unique_qptr _paintDone;
+ base::unique_qptr _ratioMenu;
+ float64 _currentRatio = 0.;
+
bool _flipped = false;
Ui::Animations::Simple _toggledBarAnimation;
rpl::variable _mode;
rpl::event_stream> _keyPresses;
+ rpl::event_stream _aspectRatioChanges;
};