editor_crop.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "editor/editor_crop.h"
  8. #include "ui/userpic_view.h"
  9. #include "styles/style_editor.h"
  10. #include "styles/style_basic.h"
  11. #include "styles/style_dialogs.h"
  12. namespace Editor {
  13. namespace {
  14. constexpr auto kETL = Qt::TopEdge | Qt::LeftEdge;
  15. constexpr auto kETR = Qt::TopEdge | Qt::RightEdge;
  16. constexpr auto kEBL = Qt::BottomEdge | Qt::LeftEdge;
  17. constexpr auto kEBR = Qt::BottomEdge | Qt::RightEdge;
  18. constexpr auto kEAll = Qt::TopEdge
  19. | Qt::LeftEdge
  20. | Qt::BottomEdge
  21. | Qt::RightEdge;
  22. std::tuple<int, int, int, int> RectEdges(const QRectF &r) {
  23. return { r.left(), r.top(), r.left() + r.width(), r.top() + r.height() };
  24. }
  25. QPoint PointOfEdge(Qt::Edges e, const QRectF &r) {
  26. switch(e) {
  27. case kETL: return QPoint(r.x(), r.y());
  28. case kETR: return QPoint(r.x() + r.width(), r.y());
  29. case kEBL: return QPoint(r.x(), r.y() + r.height());
  30. case kEBR: return QPoint(r.x() + r.width(), r.y() + r.height());
  31. default: return QPoint();
  32. }
  33. }
  34. QSizeF FlipSizeByRotation(const QSizeF &size, int angle) {
  35. return (((angle / 90) % 2) == 1) ? size.transposed() : size;
  36. }
  37. [[nodiscard]] QRectF OriginalCrop(QSize outer, QSize inner) {
  38. const auto size = inner.scaled(outer, Qt::KeepAspectRatio);
  39. return QRectF(
  40. (outer.width() - size.width()) / 2,
  41. (outer.height() - size.height()) / 2,
  42. size.width(),
  43. size.height());
  44. }
  45. } // namespace
  46. Crop::Crop(
  47. not_null<Ui::RpWidget*> parent,
  48. const PhotoModifications &modifications,
  49. const QSize &imageSize,
  50. EditorData data)
  51. : RpWidget(parent)
  52. , _pointSize(st::photoEditorCropPointSize)
  53. , _pointSizeH(_pointSize / 2.)
  54. , _innerMargins(QMarginsF(_pointSizeH, _pointSizeH, _pointSizeH, _pointSizeH)
  55. .toMargins())
  56. , _offset(_innerMargins.left(), _innerMargins.top())
  57. , _edgePointMargins(_pointSizeH, _pointSizeH, -_pointSizeH, -_pointSizeH)
  58. , _imageSize(imageSize)
  59. , _data(std::move(data))
  60. , _cropOriginal(modifications.crop.isValid()
  61. ? modifications.crop
  62. : !_data.exactSize.isEmpty()
  63. ? OriginalCrop(_imageSize, _data.exactSize)
  64. : QRectF(QPoint(), _imageSize))
  65. , _angle(modifications.angle)
  66. , _flipped(modifications.flipped)
  67. , _keepAspectRatio(_data.keepAspectRatio) {
  68. setMouseTracking(true);
  69. paintRequest(
  70. ) | rpl::start_with_next([=] {
  71. auto p = QPainter(this);
  72. p.fillPath(_painterPath, st::photoCropFadeBg);
  73. paintPoints(p);
  74. }, lifetime());
  75. }
  76. void Crop::applyTransform(
  77. const QRect &geometry,
  78. int angle,
  79. bool flipped,
  80. const QSizeF &scaledImageSize) {
  81. if (geometry.isEmpty()) {
  82. return;
  83. }
  84. setGeometry(geometry);
  85. _innerRect = QRectF(_offset, FlipSizeByRotation(scaledImageSize, angle));
  86. _ratio.w = scaledImageSize.width() / float64(_imageSize.width());
  87. _ratio.h = scaledImageSize.height() / float64(_imageSize.height());
  88. _flipped = flipped;
  89. _angle = angle;
  90. const auto cropHolder = QRectF(QPointF(), scaledImageSize);
  91. const auto cropHolderCenter = cropHolder.center();
  92. auto matrix = QTransform()
  93. .translate(cropHolderCenter.x(), cropHolderCenter.y())
  94. .scale(flipped ? -1 : 1, 1)
  95. .rotate(angle)
  96. .translate(-cropHolderCenter.x(), -cropHolderCenter.y());
  97. const auto cropHolderRotated = matrix.mapRect(cropHolder);
  98. auto cropPaint = matrix
  99. .scale(_ratio.w, _ratio.h)
  100. .mapRect(_cropOriginal)
  101. .translated(
  102. -cropHolderRotated.x() + _offset.x(),
  103. -cropHolderRotated.y() + _offset.y());
  104. // Check boundaries.
  105. const auto min = float64(st::photoEditorCropMinSize);
  106. if ((cropPaint.width() < min) || (cropPaint.height() < min)) {
  107. cropPaint.setWidth(std::max(min, cropPaint.width()));
  108. cropPaint.setHeight(std::max(min, cropPaint.height()));
  109. const auto p = cropPaint.center().toPoint();
  110. setCropPaint(std::move(cropPaint));
  111. computeDownState(p);
  112. performMove(p);
  113. clearDownState();
  114. convertCropPaintToOriginal();
  115. } else {
  116. setCropPaint(std::move(cropPaint));
  117. }
  118. }
  119. void Crop::paintPoints(QPainter &p) {
  120. p.save();
  121. p.setPen(Qt::NoPen);
  122. p.setBrush(st::photoCropPointFg);
  123. for (const auto &r : ranges::views::values(_edges)) {
  124. p.drawRect(r);
  125. }
  126. p.restore();
  127. }
  128. void Crop::setCropPaint(QRectF &&rect) {
  129. _cropPaint = std::move(rect);
  130. updateEdges();
  131. _painterPath.clear();
  132. _painterPath.addRect(_innerRect);
  133. if (_data.cropType == EditorData::CropType::Ellipse) {
  134. _painterPath.addEllipse(_cropPaint);
  135. } else if (_data.cropType == EditorData::CropType::RoundedRect) {
  136. const auto radius = std::min(_cropPaint.width(), _cropPaint.height())
  137. * Ui::ForumUserpicRadiusMultiplier();
  138. _painterPath.addRoundedRect(_cropPaint, radius, radius);
  139. } else {
  140. _painterPath.addRect(_cropPaint);
  141. }
  142. }
  143. void Crop::convertCropPaintToOriginal() {
  144. const auto cropHolder = QTransform()
  145. .scale(_ratio.w, _ratio.h)
  146. .mapRect(QRectF(QPointF(), FlipSizeByRotation(_imageSize, _angle)));
  147. const auto cropHolderCenter = cropHolder.center();
  148. const auto matrix = QTransform()
  149. .translate(cropHolderCenter.x(), cropHolderCenter.y())
  150. .rotate(-_angle)
  151. .scale((_flipped ? -1 : 1) * 1. / _ratio.w, 1. / _ratio.h)
  152. .translate(-cropHolderCenter.x(), -cropHolderCenter.y());
  153. const auto cropHolderRotated = matrix.mapRect(cropHolder);
  154. _cropOriginal = matrix
  155. .mapRect(QRectF(_cropPaint).translated(-_offset))
  156. .translated(
  157. -cropHolderRotated.x(),
  158. -cropHolderRotated.y());
  159. }
  160. void Crop::updateEdges() {
  161. const auto &s = _pointSize;
  162. const auto &m = _edgePointMargins;
  163. const auto &r = _cropPaint;
  164. for (const auto &e : { kETL, kETR, kEBL, kEBR }) {
  165. _edges[e] = QRectF(PointOfEdge(e, r), QSize(s, s)) + m;
  166. }
  167. }
  168. Qt::Edges Crop::mouseState(const QPoint &p) {
  169. for (const auto &[e, r] : _edges) {
  170. if (r.contains(p)) {
  171. return e;
  172. }
  173. }
  174. if (_cropPaint.contains(p)) {
  175. return kEAll;
  176. }
  177. return Qt::Edges();
  178. }
  179. void Crop::mousePressEvent(QMouseEvent *e) {
  180. computeDownState(e->pos());
  181. }
  182. void Crop::mouseReleaseEvent(QMouseEvent *e) {
  183. clearDownState();
  184. convertCropPaintToOriginal();
  185. }
  186. void Crop::computeDownState(const QPoint &p) {
  187. const auto edge = mouseState(p);
  188. const auto &inner = _innerRect;
  189. const auto &crop = _cropPaint;
  190. const auto &[iLeft, iTop, iRight, iBottom] = RectEdges(inner);
  191. const auto &[cLeft, cTop, cRight, cBottom] = RectEdges(crop);
  192. _down = InfoAtDown{
  193. .rect = crop,
  194. .edge = edge,
  195. .point = (p - PointOfEdge(edge, crop)),
  196. .cropRatio = (_cropOriginal.width() / _cropOriginal.height()),
  197. .borders = InfoAtDown::Borders{
  198. .left = iLeft - cLeft,
  199. .right = iRight - cRight,
  200. .top = iTop - cTop,
  201. .bottom = iBottom - cBottom,
  202. }
  203. };
  204. if (_keepAspectRatio && (edge != kEAll)) {
  205. const auto hasLeft = (edge & Qt::LeftEdge);
  206. const auto hasTop = (edge & Qt::TopEdge);
  207. const auto xSign = hasLeft ? -1 : 1;
  208. const auto ySign = hasTop ? -1 : 1;
  209. auto &xSide = (hasLeft ? _down.borders.left : _down.borders.right);
  210. auto &ySide = (hasTop ? _down.borders.top : _down.borders.bottom);
  211. const auto min = std::abs(std::min(xSign * xSide, ySign * ySide));
  212. const auto xIsMin = ((xSign * xSide) < (ySign * ySide));
  213. xSide = xSign * min;
  214. ySide = ySign * min;
  215. if (!xIsMin) {
  216. xSide *= _down.cropRatio;
  217. } else {
  218. ySide /= _down.cropRatio;
  219. }
  220. }
  221. }
  222. void Crop::clearDownState() {
  223. _down = InfoAtDown();
  224. }
  225. void Crop::performCrop(const QPoint &pos) {
  226. const auto &crop = _down.rect;
  227. const auto &pressedEdge = _down.edge;
  228. const auto hasLeft = (pressedEdge & Qt::LeftEdge);
  229. const auto hasTop = (pressedEdge & Qt::TopEdge);
  230. const auto hasRight = (pressedEdge & Qt::RightEdge);
  231. const auto hasBottom = (pressedEdge & Qt::BottomEdge);
  232. const auto diff = [&] {
  233. auto diff = pos - PointOfEdge(pressedEdge, crop) - _down.point;
  234. const auto xFactor = hasLeft ? 1 : -1;
  235. const auto yFactor = hasTop ? 1 : -1;
  236. const auto &borders = _down.borders;
  237. const auto &cropRatio = _down.cropRatio;
  238. if (_keepAspectRatio) {
  239. const auto diffSign = xFactor * yFactor;
  240. diff = (cropRatio != 1.)
  241. ? QPoint(diff.x(), (1. / cropRatio) * diff.x() * diffSign)
  242. // For square/circle.
  243. : ((diff.x() * xFactor) < (diff.y() * yFactor))
  244. ? QPoint(diff.x(), diff.x() * diffSign)
  245. : QPoint(diff.y() * diffSign, diff.y());
  246. }
  247. const auto &minSize = st::photoEditorCropMinSize;
  248. const auto xMin = xFactor * int(crop.width() - minSize);
  249. // const auto xMin = int(xFactor * crop.width()
  250. // - xFactor * minSize * ((cropRatio > 1.) ? cropRatio : 1.));
  251. const auto yMin = yFactor * int(crop.height() - minSize);
  252. // const auto yMin = int(yFactor * crop.height()
  253. // - yFactor * minSize * ((cropRatio < 1.) ? (1. / cropRatio) : 1.));
  254. const auto x = std::clamp(
  255. diff.x(),
  256. hasLeft ? borders.left : xMin,
  257. hasLeft ? xMin : borders.right);
  258. const auto y = std::clamp(
  259. diff.y(),
  260. hasTop ? borders.top : yMin,
  261. hasTop ? yMin : borders.bottom);
  262. return QPoint(x, y);
  263. }();
  264. setCropPaint(crop - QMargins(
  265. hasLeft ? diff.x() : 0,
  266. hasTop ? diff.y() : 0,
  267. hasRight ? -diff.x() : 0,
  268. hasBottom ? -diff.y() : 0));
  269. }
  270. void Crop::performMove(const QPoint &pos) {
  271. const auto &inner = _down.rect;
  272. const auto &b = _down.borders;
  273. const auto diffX = std::clamp(pos.x() - _down.point.x(), b.left, b.right);
  274. const auto diffY = std::clamp(pos.y() - _down.point.y(), b.top, b.bottom);
  275. setCropPaint(inner.translated(diffX, diffY));
  276. }
  277. void Crop::mouseMoveEvent(QMouseEvent *e) {
  278. const auto pos = e->pos();
  279. const auto pressedEdge = _down.edge;
  280. if (pressedEdge) {
  281. if (pressedEdge == kEAll) {
  282. performMove(pos);
  283. } else if (pressedEdge) {
  284. performCrop(pos);
  285. }
  286. update();
  287. }
  288. const auto edge = pressedEdge ? pressedEdge : mouseState(pos);
  289. const auto cursor = ((edge == kETL) || (edge == kEBR))
  290. ? style::cur_sizefdiag
  291. : ((edge == kETR) || (edge == kEBL))
  292. ? style::cur_sizebdiag
  293. : (edge == kEAll)
  294. ? style::cur_sizeall
  295. : style::cur_default;
  296. setCursor(cursor);
  297. }
  298. style::margins Crop::cropMargins() const {
  299. return _innerMargins;
  300. }
  301. QRect Crop::saveCropRect() {
  302. const auto savedCrop = _cropOriginal.toRect();
  303. return (!savedCrop.topLeft().isNull() || (savedCrop.size() != _imageSize))
  304. ? savedCrop
  305. : QRect();
  306. }
  307. } // namespace Editor