[img-editor] Locked crop frame as stencil and clipped output by shape.

This commit is contained in:
23rd
2026-04-13 20:10:35 +03:00
parent 1d035109ea
commit 58c7467720
5 changed files with 71 additions and 13 deletions
@@ -475,6 +475,7 @@ void OpenPhotoEditorForSticker(
.exactSize = QSize(kStickerSide, kStickerSide), .exactSize = QSize(kStickerSide, kStickerSide),
.cropType = Editor::EditorData::CropType::RoundedRect, .cropType = Editor::EditorData::CropType::RoundedRect,
.keepAspectRatio = true, .keepAspectRatio = true,
.fixedCrop = true,
}); });
const auto raw = editor.get(); const auto raw = editor.get();
@@ -177,6 +177,10 @@ void Crop::paintFrame(QPainter &p) {
p.save(); p.save();
p.setRenderHint(QPainter::Antialiasing, true); p.setRenderHint(QPainter::Antialiasing, true);
p.fillPath(frameShape, st::photoCropPointFg); p.fillPath(frameShape, st::photoCropPointFg);
if (_data.fixedCrop) {
p.restore();
return;
}
{ {
const auto cornerLength = std::min( const auto cornerLength = std::min(
float64(st::photoEditorCropPointSize * 2), float64(st::photoEditorCropPointSize * 2),
@@ -286,6 +290,10 @@ void Crop::convertCropPaintToOriginal() {
} }
void Crop::updateEdges() { void Crop::updateEdges() {
if (_data.fixedCrop) {
_edges.clear();
return;
}
const auto &s = _pointSize; const auto &s = _pointSize;
const auto &m = _edgePointMargins; const auto &m = _edgePointMargins;
const auto &r = _cropPaint; const auto &r = _cropPaint;
@@ -338,6 +346,9 @@ Qt::Edges Crop::mouseState(const QPoint &p) {
} }
void Crop::mousePressEvent(QMouseEvent *e) { void Crop::mousePressEvent(QMouseEvent *e) {
if (_data.fixedCrop) {
return;
}
computeDownState(e->pos()); computeDownState(e->pos());
if (_down.edge) { if (_down.edge) {
setGridVisible(true, false); setGridVisible(true, false);
@@ -345,6 +356,9 @@ void Crop::mousePressEvent(QMouseEvent *e) {
} }
void Crop::mouseReleaseEvent(QMouseEvent *e) { void Crop::mouseReleaseEvent(QMouseEvent *e) {
if (_data.fixedCrop) {
return;
}
const auto hadEdge = bool(_down.edge); const auto hadEdge = bool(_down.edge);
if (hadEdge) { if (hadEdge) {
setGridVisible(false, true); setGridVisible(false, true);
@@ -474,6 +488,9 @@ void Crop::performMove(const QPoint &pos) {
} }
void Crop::mouseMoveEvent(QMouseEvent *e) { void Crop::mouseMoveEvent(QMouseEvent *e) {
if (_data.fixedCrop) {
return;
}
const auto pos = e->pos(); const auto pos = e->pos();
const auto pressedEdge = _down.edge; const auto pressedEdge = _down.edge;
@@ -237,6 +237,7 @@ PhotoEditor::PhotoEditor(
std::move(show), std::move(show),
_brushes, _brushes,
_brushTool)) { _brushTool)) {
_modifications.cropType = data.cropType;
sizeValue( sizeValue(
) | rpl::on_next([=](const QSize &size) { ) | rpl::on_next([=](const QSize &size) {
@@ -9,8 +9,40 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "editor/scene/scene.h" #include "editor/scene/scene.h"
#include "ui/painter.h" #include "ui/painter.h"
#include "ui/userpic_view.h"
namespace Editor { namespace Editor {
namespace {
void ApplyShapeMask(QImage &image, EditorData::CropType type) {
if (type == EditorData::CropType::Rect) {
return;
}
if (image.format() != QImage::Format_ARGB32_Premultiplied) {
image = image.convertToFormat(QImage::Format_ARGB32_Premultiplied);
}
auto mask = QImage(image.size(), QImage::Format_ARGB32_Premultiplied);
mask.fill(Qt::transparent);
{
auto p = QPainter(&mask);
auto hq = PainterHighQualityEnabler(p);
p.setPen(Qt::NoPen);
p.setBrush(Qt::white);
const auto rect = QRectF(QPointF(), QSizeF(image.size()));
if (type == EditorData::CropType::Ellipse) {
p.drawEllipse(rect);
} else {
const auto radius = std::min(rect.width(), rect.height())
* Ui::ForumUserpicRadiusMultiplier();
p.drawRoundedRect(rect, radius, radius);
}
}
auto p = QPainter(&image);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.drawImage(0, 0, mask);
}
} // namespace
QImage ImageModified(QImage image, const PhotoModifications &mods) { QImage ImageModified(QImage image, const PhotoModifications &mods) {
Expects(!image.isNull()); Expects(!image.isNull());
@@ -32,6 +64,7 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
auto cropped = mods.crop.isValid() auto cropped = mods.crop.isValid()
? image.copy(mods.crop) ? image.copy(mods.crop)
: image; : image;
ApplyShapeMask(cropped, mods.cropType);
QTransform transform; QTransform transform;
if (mods.flipped) { if (mods.flipped) {
transform.scale(-1, 1); transform.scale(-1, 1);
@@ -43,7 +76,11 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
} }
bool PhotoModifications::empty() const { bool PhotoModifications::empty() const {
return !angle && !flipped && !crop.isValid() && !paint; return !angle
&& !flipped
&& !crop.isValid()
&& cropType == EditorData::CropType::Rect
&& !paint;
} }
PhotoModifications::operator bool() const { PhotoModifications::operator bool() const {
@@ -11,18 +11,6 @@ namespace Editor {
class Scene; class Scene;
struct PhotoModifications {
int angle = 0;
bool flipped = false;
QRect crop;
std::shared_ptr<Scene> paint = nullptr;
[[nodiscard]] bool empty() const;
[[nodiscard]] explicit operator bool() const;
~PhotoModifications();
};
struct EditorData { struct EditorData {
enum class CropType { enum class CropType {
Rect, Rect,
@@ -35,6 +23,20 @@ struct EditorData {
QSize exactSize; QSize exactSize;
CropType cropType = CropType::Rect; CropType cropType = CropType::Rect;
bool keepAspectRatio = false; bool keepAspectRatio = false;
bool fixedCrop = false;
};
struct PhotoModifications {
int angle = 0;
bool flipped = false;
QRect crop;
EditorData::CropType cropType = EditorData::CropType::Rect;
std::shared_ptr<Scene> paint = nullptr;
[[nodiscard]] bool empty() const;
[[nodiscard]] explicit operator bool() const;
~PhotoModifications();
}; };
[[nodiscard]] QImage ImageModified( [[nodiscard]] QImage ImageModified(