Refactored crop widget in photo editor.

This commit is contained in:
23rd 2021-02-18 09:37:12 +03:00
parent c9affe0da5
commit 09768ce28a
4 changed files with 109 additions and 110 deletions

View file

@ -21,11 +21,11 @@ constexpr auto kEAll = Qt::TopEdge
| Qt::BottomEdge | Qt::BottomEdge
| Qt::RightEdge; | Qt::RightEdge;
std::tuple<int, int, int, int> RectEdges(const QRect &r) { std::tuple<int, int, int, int> RectEdges(const QRectF &r) {
return { r.left(), r.top(), r.left() + r.width(), r.top() + r.height() }; 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) { switch(e) {
case kETL: return QPoint(r.x(), r.y()); case kETL: return QPoint(r.x(), r.y());
case kETR: return QPoint(r.x() + r.width(), 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 } // namespace
Crop::Crop( Crop::Crop(
@ -47,17 +51,13 @@ Crop::Crop(
, _innerMargins(QMarginsF(_pointSizeH, _pointSizeH, _pointSizeH, _pointSizeH) , _innerMargins(QMarginsF(_pointSizeH, _pointSizeH, _pointSizeH, _pointSizeH)
.toMargins()) .toMargins())
, _offset(_innerMargins.left(), _innerMargins.top()) , _offset(_innerMargins.left(), _innerMargins.top())
, _edgePointMargins(_pointSizeH, _pointSizeH, -_pointSizeH, -_pointSizeH) { , _edgePointMargins(_pointSizeH, _pointSizeH, -_pointSizeH, -_pointSizeH)
, _imageSize(imageSize)
_angle = modifications.angle; , _cropOriginal(modifications.crop.isValid()
_flipped = modifications.flipped; ? modifications.crop
_cropRect = modifications.crop; : QRectF(QPoint(), _imageSize))
if (_cropRect.isValid()) { , _angle(modifications.angle)
const auto inner = QRect(QPoint(), imageSize); , _flipped(modifications.flipped) {
_innerRect = QRect(
QPoint(),
QMatrix().rotate(-_angle).mapRect(inner).size());
}
setMouseTracking(true); setMouseTracking(true);
@ -66,63 +66,61 @@ Crop::Crop(
Painter p(this); Painter p(this);
p.fillPath(_painterPath, st::photoCropFadeBg); p.fillPath(_painterPath, st::photoCropFadeBg);
paintPoints(p); paintPoints(p);
}, lifetime()); }, 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()) { if (geometry.isEmpty()) {
return; return;
} }
setGeometry(geometry); 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 cropHolder = QRectF(QPointF(), scaledImageSize);
const auto wasInner = _innerRect.isEmpty() ? nowInner : _innerRect; const auto cropHolderCenter = cropHolder.center();
const auto nowInnerF = QRectF(QPointF(), QSizeF(nowInner.size()));
const auto wasInnerF = QRectF(QPointF(), QSizeF(wasInner.size()));
_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()) { const auto cropHolderRotated = matrix.mapRect(cropHolder);
setCropRect(_innerRect.translated(-_offset));
}
const auto angleTo = (angle - _angle) * (flipped ? -1 : 1); auto cropPaint = matrix
const auto flippedChanges = (_flipped != flipped); .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);
const auto nowCropRect = resizedCropRect(wasInnerF, nowInnerRotated)
.translated(nowInnerRotated.topLeft());
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. // Check boundaries.
const auto p = _cropRectPaint.center(); 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 p = cropPaint.center().toPoint();
setCropPaint(std::move(cropPaint));
computeDownState(p); computeDownState(p);
performMove(p); performMove(p);
clearDownState(); clearDownState();
}
_flipped = flipped; convertCropPaintToOriginal();
_angle = angle; } else {
setCropPaint(std::move(cropPaint));
}
} }
void Crop::paintPoints(Painter &p) { void Crop::paintPoints(Painter &p) {
@ -135,25 +133,41 @@ void Crop::paintPoints(Painter &p) {
p.restore(); p.restore();
} }
void Crop::setCropRect(QRect &&rect) { void Crop::setCropPaint(QRectF &&rect) {
_cropRect = std::move(rect); _cropPaint = std::move(rect);
_cropRectPaint = _cropRect.translated(_offset);
updateEdges(); updateEdges();
_painterPath.clear(); _painterPath.clear();
_painterPath.addRect(_innerRect); _painterPath.addRect(_innerRect);
_painterPath.addRect(_cropRectPaint); _painterPath.addRect(_cropPaint);
} }
void Crop::setCropRectPaint(QRect &&rect) { void Crop::convertCropPaintToOriginal() {
rect.translate(-_offset); const auto cropHolder = QMatrix()
setCropRect(std::move(rect)); .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() { void Crop::updateEdges() {
const auto &s = _pointSize; const auto &s = _pointSize;
const auto &m = _edgePointMargins; const auto &m = _edgePointMargins;
const auto &r = _cropRectPaint; const auto &r = _cropPaint;
for (const auto &e : { kETL, kETR, kEBL, kEBR }) { for (const auto &e : { kETL, kETR, kEBL, kEBR }) {
_edges[e] = QRectF(PointOfEdge(e, r), QSize(s, s)) + m; _edges[e] = QRectF(PointOfEdge(e, r), QSize(s, s)) + m;
} }
@ -165,7 +179,7 @@ Qt::Edges Crop::mouseState(const QPoint &p) {
return e; return e;
} }
} }
if (_cropRectPaint.contains(p)) { if (_cropPaint.contains(p)) {
return kEAll; return kEAll;
} }
return Qt::Edges(); return Qt::Edges();
@ -177,12 +191,13 @@ void Crop::mousePressEvent(QMouseEvent *e) {
void Crop::mouseReleaseEvent(QMouseEvent *e) { void Crop::mouseReleaseEvent(QMouseEvent *e) {
clearDownState(); clearDownState();
convertCropPaintToOriginal();
} }
void Crop::computeDownState(const QPoint &p) { void Crop::computeDownState(const QPoint &p) {
const auto edge = mouseState(p); const auto edge = mouseState(p);
const auto &inner = _innerRect; const auto &inner = _innerRect;
const auto &crop = _cropRectPaint; const auto &crop = _cropPaint;
const auto [iLeft, iTop, iRight, iBottom] = RectEdges(inner); const auto [iLeft, iTop, iRight, iBottom] = RectEdges(inner);
const auto [cLeft, cTop, cRight, cBottom] = RectEdges(crop); const auto [cLeft, cTop, cRight, cBottom] = RectEdges(crop);
_down = InfoAtDown{ _down = InfoAtDown{
@ -215,8 +230,10 @@ void Crop::performCrop(const QPoint &pos) {
const auto vFactor = hasTop ? 1 : -1; const auto vFactor = hasTop ? 1 : -1;
const auto &borders = _down.borders; const auto &borders = _down.borders;
const auto hMin = hFactor * crop.width() - hFactor * st::cropMinSize; const auto hMin = int(
const auto vMin = vFactor * crop.height() - vFactor * st::cropMinSize; hFactor * crop.width() - hFactor * st::cropMinSize);
const auto vMin = int(
vFactor * crop.height() - vFactor * st::cropMinSize);
const auto x = std::clamp( const auto x = std::clamp(
diff.x(), diff.x(),
@ -232,7 +249,7 @@ void Crop::performCrop(const QPoint &pos) {
} }
return QPoint(x, y); return QPoint(x, y);
}(); }();
setCropRectPaint(crop - QMargins( setCropPaint(crop - QMargins(
hasLeft ? diff.x() : 0, hasLeft ? diff.x() : 0,
hasTop ? diff.y() : 0, hasTop ? diff.y() : 0,
hasRight ? -diff.x() : 0, hasRight ? -diff.x() : 0,
@ -244,7 +261,7 @@ void Crop::performMove(const QPoint &pos) {
const auto &b = _down.borders; const auto &b = _down.borders;
const auto diffX = std::clamp(pos.x() - _down.point.x(), b.left, b.right); 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); 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) { void Crop::mouseMoveEvent(QMouseEvent *e) {
@ -272,29 +289,12 @@ void Crop::mouseMoveEvent(QMouseEvent *e) {
setCursor(cursor); setCursor(cursor);
} }
QRect Crop::innerRect() const {
return _innerRect;
}
style::margins Crop::cropMargins() const { style::margins Crop::cropMargins() const {
return _innerMargins; return _innerMargins;
} }
QRect Crop::saveCropRect(const QRect &from, const QRect &to) { QRect Crop::saveCropRect() {
return resizedCropRect(QRectF(from), QRectF(to)).toRect(); return _cropOriginal.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));
} }
} // namespace Editor } // namespace Editor

View file

@ -22,11 +22,12 @@ public:
const PhotoModifications &modifications, const PhotoModifications &modifications,
const QSize &imageSize); const QSize &imageSize);
void applyTransform(QRect geometry, int angle, bool flipped); void applyTransform(
[[nodiscard]] QRect innerRect() const; const QRect &geometry,
[[nodiscard]] QRect saveCropRect( int angle,
const QRect &from, bool flipped,
const QRect &to); const QSizeF &scaledImageSize);
[[nodiscard]] QRect saveCropRect();
[[nodiscard]] style::margins cropMargins() const; [[nodiscard]] style::margins cropMargins() const;
protected: protected:
@ -36,7 +37,7 @@ protected:
private: private:
struct InfoAtDown { struct InfoAtDown {
QRect rect; QRectF rect;
Qt::Edges edge = 0; Qt::Edges edge = 0;
QPoint point; QPoint point;
@ -48,17 +49,12 @@ private:
} borders; } borders;
}; };
[[nodiscard]] QRectF resizedCropRect(
const QRectF &from,
const QRectF &to);
void paintPoints(Painter &p); void paintPoints(Painter &p);
void updateEdges(); void updateEdges();
[[nodiscard]] QPoint pointOfEdge(Qt::Edges e) const; [[nodiscard]] QPoint pointOfEdge(Qt::Edges e) const;
void setCropRect(QRect &&rect); void setCropPaint(QRectF &&rect);
void setCropRectPaint(QRect &&rect); void convertCropPaintToOriginal();
void rotate(bool clockwise = true);
void computeDownState(const QPoint &p); void computeDownState(const QPoint &p);
void clearDownState(); void clearDownState();
@ -71,15 +67,18 @@ private:
const style::margins _innerMargins; const style::margins _innerMargins;
const QPoint _offset; const QPoint _offset;
const QMarginsF _edgePointMargins; const QMarginsF _edgePointMargins;
const QSize _imageSize;
base::flat_map<Qt::Edges, QRectF> _edges; base::flat_map<Qt::Edges, QRectF> _edges;
// Is translated with the inner indentation. struct {
QRect _cropRectPaint; float64 w = 0.;
// Is not. float64 h = 0.;
QRect _cropRect; } _ratio;
QRect _innerRect; QRectF _cropPaint;
QRectF _cropOriginal;
QRectF _innerRect;
QPainterPath _painterPath; QPainterPath _painterPath;

View file

@ -19,6 +19,9 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
mods.paint->render(&p, image.rect()); mods.paint->render(&p, image.rect());
} }
auto cropped = mods.crop.isValid()
? image.copy(mods.crop)
: image;
QTransform transform; QTransform transform;
if (mods.flipped) { if (mods.flipped) {
transform.scale(-1, 1); transform.scale(-1, 1);
@ -26,11 +29,7 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) {
if (mods.angle) { if (mods.angle) {
transform.rotate(mods.angle); transform.rotate(mods.angle);
} }
auto newImage = image.transformed(transform); return cropped.transformed(transform);
if (mods.crop.isValid()) {
newImage = newImage.copy(mods.crop);
}
return newImage;
} }
bool PhotoModifications::empty() const { bool PhotoModifications::empty() const {

View file

@ -39,13 +39,13 @@ PhotoEditorContent::PhotoEditorContent(
if (size.isEmpty()) { if (size.isEmpty()) {
return; return;
} }
const auto imageSize = [&] { const auto imageSizeF = [&] {
const auto rotatedSize = const auto rotatedSize =
FlipSizeByRotation(size, mods.angle); FlipSizeByRotation(size, mods.angle);
const auto m = _crop->cropMargins(); const auto m = _crop->cropMargins();
const auto sizeForCrop = rotatedSize const auto sizeForCrop = rotatedSize
- QSize(m.left() + m.right(), m.top() + m.bottom()); - 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()) if ((originalSize.width() > sizeForCrop.width())
|| (originalSize.height() > sizeForCrop.height())) { || (originalSize.height() > sizeForCrop.height())) {
return originalSize.scaled( return originalSize.scaled(
@ -54,6 +54,7 @@ PhotoEditorContent::PhotoEditorContent(
} }
return originalSize; return originalSize;
}(); }();
const auto imageSize = QSize(imageSizeF.width(), imageSizeF.height());
_imageRect = QRect( _imageRect = QRect(
QPoint(-imageSize.width() / 2, -imageSize.height() / 2), QPoint(-imageSize.width() / 2, -imageSize.height() / 2),
imageSize); imageSize);
@ -69,7 +70,7 @@ PhotoEditorContent::PhotoEditorContent(
_crop->applyTransform( _crop->applyTransform(
geometry + _crop->cropMargins(), geometry + _crop->cropMargins(),
mods.angle, mods.angle,
mods.flipped); mods.flipped, imageSizeF);
_paint->applyTransform(geometry, mods.angle, mods.flipped); _paint->applyTransform(geometry, mods.angle, mods.flipped);
}, lifetime()); }, lifetime());
@ -92,7 +93,7 @@ void PhotoEditorContent::applyModifications(
} }
void PhotoEditorContent::save(PhotoModifications &modifications) { void PhotoEditorContent::save(PhotoModifications &modifications) {
modifications.crop = _crop->saveCropRect(_imageRect, _photo->rect()); modifications.crop = _crop->saveCropRect();
_paint->keepResult(); _paint->keepResult();
if (!modifications.paint) { if (!modifications.paint) {
modifications.paint = _paint->saveScene(); modifications.paint = _paint->saveScene();