[img-editor] Added corner rounding level picker for sticker creation.

This commit is contained in:
23rd
2026-04-22 11:53:15 +03:00
parent caf68ba5e6
commit 47489fd513
11 changed files with 172 additions and 7 deletions
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 21 21">
<g fill="none" fill-rule="evenodd" stroke="white" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" transform="translate(2 2)">
<path d="m.5 4.5c0-2.209139 1.79086-4 4-4"/>
<path d="m16.5 4.5c0-2.209139-1.79086-4-4-4"/>
<path d="m16.5 12.5c0 2.209139-1.79086 4-4 4"/>
<path d="m.5 12.5c0 2.209139 1.79086 4 4 4"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 426 B

+6
View File
@@ -7222,6 +7222,12 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
"lng_photo_editor_crop_square" = "Square";
"lng_photo_editor_crop_free" = "Free";
"lng_photo_editor_corners_about" = "Choose how rounded the sticker corners should look.";
"lng_photo_editor_corners_large" = "Large";
"lng_photo_editor_corners_medium" = "Medium";
"lng_photo_editor_corners_small" = "Small";
"lng_photo_editor_corners_none" = "None";
"lng_voice_speed_slow" = "Slow";
"lng_voice_speed_normal" = "Normal";
"lng_voice_speed_medium" = "Medium";
+14
View File
@@ -95,6 +95,20 @@ photoEditorCropRatioButton: IconButton(photoEditorRotateButton) {
}
photoEditorCropRatioIconActive: icon {{ "photo_editor/ratio-23x23", photoEditorButtonIconFgActive, point(0px, 1px) }};
photoEditorCornersButton: IconButton(photoEditorRotateButton) {
icon: icon {{ "photo_editor/corners-23x23", photoEditorButtonIconFg, point(0px, 1px) }};
iconOver: icon {{ "photo_editor/corners-23x23", photoEditorButtonIconFgOver, point(0px, 1px) }};
}
photoEditorCornersIconActive: icon {{ "photo_editor/corners-23x23", photoEditorButtonIconFgActive, point(0px, 1px) }};
photoEditorCornersMenuAboutLabel: FlatLabel(defaultFlatLabel) {
style: TextStyle(defaultTextStyle) {
font: font(11px);
}
minWidth: 136px;
}
photoEditorCornersMenuAboutPosition: point(12px, 4px);
photoEditorCropRatioMenu: PopupMenu(popupMenuWithIcons) {
menu: Menu(menuWithIcons) {
widthMin: 108px;
+22 -4
View File
@@ -82,7 +82,8 @@ Crop::Crop(
: QRectF(QPoint(), _imageSize))
, _angle(modifications.angle)
, _flipped(modifications.flipped)
, _keepAspectRatio(_data.keepAspectRatio) {
, _keepAspectRatio(_data.keepAspectRatio)
, _cornersLevel(modifications.cornersLevel) {
setMouseTracking(true);
@@ -158,9 +159,15 @@ QPainterPath Crop::cropPath() const {
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);
const auto multiplier = RoundedCornersMultiplier(_cornersLevel);
if (multiplier <= 0.) {
result.addRect(_cropPaint);
} else {
const auto radius = std::min(
_cropPaint.width(),
_cropPaint.height()) * multiplier;
result.addRoundedRect(_cropPaint, radius, radius);
}
} else {
result.addRect(_cropPaint);
}
@@ -565,6 +572,17 @@ void Crop::setAspectRatio(float64 ratio) {
update();
}
void Crop::setCornersLevel(RoundedCornersLevel level) {
if (_cornersLevel == level) {
return;
}
_cornersLevel = level;
_painterPath.clear();
_painterPath.addRect(_innerRect);
_painterPath.addPath(cropPath());
update();
}
QRect Crop::saveCropRect() {
const auto savedCrop = _cropOriginal.toRect();
return (!savedCrop.topLeft().isNull() || (savedCrop.size() != _imageSize))
@@ -32,6 +32,7 @@ public:
[[nodiscard]] QRect saveCropRect();
[[nodiscard]] style::margins cropMargins() const;
void setAspectRatio(float64 ratio);
void setCornersLevel(RoundedCornersLevel level);
protected:
void mousePressEvent(QMouseEvent *e) override;
@@ -99,6 +100,8 @@ private:
bool _keepAspectRatio = false;
RoundedCornersLevel _cornersLevel = RoundedCornersLevel::Large;
};
} // namespace Editor
@@ -295,6 +295,12 @@ PhotoEditor::PhotoEditor(
_content->applyAspectRatio(ratio);
}, lifetime());
_controls->cornersLevelChanges(
) | rpl::on_next([=](RoundedCornersLevel level) {
_modifications.cornersLevel = level;
_content->applyModifications(_modifications);
}, lifetime());
_controls->paintModeRequests(
) | rpl::on_next([=] {
_mode = PhotoEditorMode{
@@ -14,10 +14,19 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
namespace Editor {
namespace {
void ApplyShapeMask(QImage &image, EditorData::CropType type) {
void ApplyShapeMask(
QImage &image,
EditorData::CropType type,
RoundedCornersLevel cornersLevel) {
if (type == EditorData::CropType::Rect) {
return;
}
const auto multiplier = (type == EditorData::CropType::RoundedRect)
? RoundedCornersMultiplier(cornersLevel)
: Ui::ForumUserpicRadiusMultiplier();
if (type == EditorData::CropType::RoundedRect && multiplier <= 0.) {
return;
}
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
@@ -33,7 +42,7 @@ void ApplyShapeMask(QImage &image, EditorData::CropType type) {
p.drawEllipse(rect);
} else {
const auto radius = std::min(rect.width(), rect.height())
* Ui::ForumUserpicRadiusMultiplier();
* multiplier;
p.drawRoundedRect(rect, radius, radius);
}
}
@@ -44,6 +53,16 @@ void ApplyShapeMask(QImage &image, EditorData::CropType type) {
} // namespace
float64 RoundedCornersMultiplier(RoundedCornersLevel level) {
switch (level) {
case RoundedCornersLevel::Large: return Ui::ForumUserpicRadiusMultiplier();
case RoundedCornersLevel::Medium: return 0.2;
case RoundedCornersLevel::Small: return 0.12;
case RoundedCornersLevel::None: return 0.;
}
Unexpected("Unknown RoundedCornersLevel in RoundedCornersMultiplier.");
}
QImage ImageModified(QImage image, const PhotoModifications &mods) {
Expects(!image.isNull());
@@ -64,7 +83,7 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
auto cropped = mods.crop.isValid()
? image.copy(mods.crop)
: image;
ApplyShapeMask(cropped, mods.cropType);
ApplyShapeMask(cropped, mods.cropType, mods.cornersLevel);
QTransform transform;
if (mods.flipped) {
transform.scale(-1, 1);
@@ -11,6 +11,15 @@ namespace Editor {
class Scene;
enum class RoundedCornersLevel {
Large,
Medium,
Small,
None,
};
[[nodiscard]] float64 RoundedCornersMultiplier(RoundedCornersLevel level);
struct EditorData {
enum class CropType {
Rect,
@@ -31,6 +40,7 @@ struct PhotoModifications {
bool flipped = false;
QRect crop;
EditorData::CropType cropType = EditorData::CropType::Rect;
RoundedCornersLevel cornersLevel = RoundedCornersLevel::Large;
std::shared_ptr<Scene> paint = nullptr;
[[nodiscard]] bool empty() const;
@@ -96,6 +96,7 @@ PhotoEditorContent::PhotoEditorContent(
geometry + _crop->cropMargins(),
mods.angle,
mods.flipped, imageSizeF);
_crop->setCornersLevel(mods.cornersLevel);
_paint->applyTransform(geometry, mods.angle, mods.flipped);
_innerRect = geometry;
@@ -12,6 +12,7 @@ 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/menu/menu_multiline_action.h"
#include "ui/widgets/popup_menu.h"
#include "ui/wrap/fade_wrap.h"
#include "ui/painter.h"
@@ -276,6 +277,11 @@ PhotoEditorControls::PhotoEditorControls(
: base::make_unique_q<Ui::IconButton>(
_transformButtons,
st::photoEditorCropRatioButton))
, _cornersButton((data.cropType == EditorData::CropType::RoundedRect)
? base::make_unique_q<Ui::IconButton>(
_transformButtons,
st::photoEditorCornersButton)
: nullptr)
, _transformDone(base::make_unique_q<EdgeButton>(
_transformButtons,
(data.confirm.isEmpty() ? tr::lng_box_done(tr::now) : data.confirm),
@@ -516,6 +522,69 @@ PhotoEditorControls::PhotoEditorControls(
});
}
_currentCornersLevel = modifications.cornersLevel;
if (_cornersButton) {
const auto updateIcon = [=] {
const auto active = (_currentCornersLevel
!= RoundedCornersLevel::Large);
const auto icon = active
? &st::photoEditorCornersIconActive
: nullptr;
_cornersButton->setIconOverride(icon, icon);
};
updateIcon();
_cornersButton->setClickedCallback([=] {
_cornersMenu = base::make_unique_q<Ui::PopupMenu>(
_cornersButton.get(),
st::photoEditorCropRatioMenu);
_cornersMenu->setForcedOrigin(
Ui::PanelAnimation::Origin::BottomRight);
auto about = base::make_unique_q<Ui::Menu::MultilineAction>(
_cornersMenu->menu(),
_cornersMenu->menu()->st(),
st::photoEditorCornersMenuAboutLabel,
st::photoEditorCornersMenuAboutPosition,
TextWithEntities{
tr::lng_photo_editor_corners_about(tr::now),
});
_cornersMenu->addAction(std::move(about));
_cornersMenu->addSeparator();
const auto check = &st::mediaPlayerMenuCheck;
const auto add = [&](
const QString &text,
RoundedCornersLevel level) {
const auto selected = (_currentCornersLevel == level);
_cornersMenu->addAction(
text,
[=] {
if (_currentCornersLevel == level) {
return;
}
_currentCornersLevel = level;
updateIcon();
_cornersLevelChanges.fire_copy(level);
},
selected ? check : nullptr);
};
add(
tr::lng_photo_editor_corners_large(tr::now),
RoundedCornersLevel::Large);
add(
tr::lng_photo_editor_corners_medium(tr::now),
RoundedCornersLevel::Medium);
add(
tr::lng_photo_editor_corners_small(tr::now),
RoundedCornersLevel::Small);
add(
tr::lng_photo_editor_corners_none(tr::now),
RoundedCornersLevel::None);
const auto button = _cornersButton.get();
const auto bottomRight = button->mapToGlobal(
QPoint(button->width(), 0));
_cornersMenu->popup(bottomRight);
});
}
updateInputMask();
}
@@ -564,6 +633,11 @@ rpl::producer<float64> PhotoEditorControls::aspectRatioChanges() const {
return _aspectRatioChanges.events();
}
auto PhotoEditorControls::cornersLevelChanges() const
-> rpl::producer<RoundedCornersLevel> {
return _cornersLevelChanges.events();
}
int PhotoEditorControls::bottomButtonsTop() const {
return height()
- st::photoEditorControlsBottomSkip
@@ -48,6 +48,8 @@ public:
[[nodiscard]] rpl::producer<QPoint> colorLinePositionValue() const;
[[nodiscard]] rpl::producer<bool> colorLineShownValue() const;
[[nodiscard]] rpl::producer<float64> aspectRatioChanges() const;
[[nodiscard]] auto cornersLevelChanges() const
-> rpl::producer<RoundedCornersLevel>;
[[nodiscard]] bool animating() const;
@@ -77,6 +79,7 @@ private:
const base::unique_qptr<Ui::IconButton> _rotateButton;
const base::unique_qptr<Ui::IconButton> _paintModeButton;
const base::unique_qptr<Ui::IconButton> _cropRatioButton;
const base::unique_qptr<Ui::IconButton> _cornersButton;
const base::unique_qptr<EdgeButton> _transformDone;
const base::unique_qptr<EdgeButton> _paintCancel;
@@ -88,7 +91,9 @@ private:
const base::unique_qptr<EdgeButton> _paintDone;
base::unique_qptr<Ui::PopupMenu> _ratioMenu;
base::unique_qptr<Ui::PopupMenu> _cornersMenu;
float64 _currentRatio = 0.;
RoundedCornersLevel _currentCornersLevel = RoundedCornersLevel::Large;
bool _flipped = false;
@@ -97,6 +102,7 @@ private:
rpl::variable<PhotoEditorMode> _mode;
rpl::event_stream<not_null<QKeyEvent*>> _keyPresses;
rpl::event_stream<float64> _aspectRatioChanges;
rpl::event_stream<RoundedCornersLevel> _cornersLevelChanges;
};