From 5b6bddd7fca220bdd86b979f660a4e9d20c1eeb8 Mon Sep 17 00:00:00 2001 From: 23rd <23rd@vivaldi.net> Date: Sat, 13 Feb 2021 07:29:31 +0300 Subject: [PATCH] Added initial implementation of mouse drawing in photo editor. --- Telegram/CMakeLists.txt | 2 + Telegram/SourceFiles/editor/editor_paint.cpp | 133 ++++++++++++++++++ Telegram/SourceFiles/editor/editor_paint.h | 49 +++++++ Telegram/SourceFiles/editor/photo_editor.cpp | 12 +- .../editor/photo_editor_common.cpp | 20 +++ .../SourceFiles/editor/photo_editor_common.h | 17 ++- .../editor/photo_editor_content.cpp | 19 ++- .../SourceFiles/editor/photo_editor_content.h | 6 +- .../editor/photo_editor_controls.cpp | 7 + .../editor/photo_editor_controls.h | 2 + 10 files changed, 254 insertions(+), 13 deletions(-) create mode 100644 Telegram/SourceFiles/editor/editor_paint.cpp create mode 100644 Telegram/SourceFiles/editor/editor_paint.h diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 83fac6e3d..c991f29a1 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -510,6 +510,8 @@ PRIVATE dialogs/dialogs_widget.h editor/editor_crop.cpp editor/editor_crop.h + editor/editor_paint.cpp + editor/editor_paint.h editor/photo_editor.cpp editor/photo_editor.h editor/photo_editor_common.cpp diff --git a/Telegram/SourceFiles/editor/editor_paint.cpp b/Telegram/SourceFiles/editor/editor_paint.cpp new file mode 100644 index 000000000..c86c5705e --- /dev/null +++ b/Telegram/SourceFiles/editor/editor_paint.cpp @@ -0,0 +1,133 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "editor/editor_paint.h" + +#include "base/event_filter.h" +#include "styles/style_boxes.h" + +#include +#include +#include + +namespace Editor { +namespace { + +constexpr auto kViewStyle = "QGraphicsView {\ + background-color: transparent;\ + border: 0px\ + }"_cs; + +std::shared_ptr EnsureScene(PhotoModifications &mods) { + if (!mods.paint) { + mods.paint = std::make_shared(nullptr); + } + return mods.paint; +} + +} // namespace + +Paint::Paint( + not_null parent, + PhotoModifications &modifications, + const QSize &imageSize) +: RpWidget(parent) +, _scene(EnsureScene(modifications)) +, _view(base::make_unique_q(_scene.get(), this)) +, _imageSize(imageSize) { + Expects(modifications.paint != nullptr); + + _view->show(); + _view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + _view->setStyleSheet(kViewStyle.utf8()); + + _scene->setSceneRect(0, 0, imageSize.width(), imageSize.height()); + + initDrawing(); + +} + +void Paint::applyTransform(QRect geometry, int angle, bool flipped) { + if (geometry.isEmpty()) { + return; + } + setGeometry(geometry); + const auto size = geometry.size(); + + const auto rotatedImageSize = QMatrix() + .rotate(angle) + .mapRect(QRect(QPoint(), _imageSize)); + + const auto ratioW = size.width() / float64(rotatedImageSize.width()) + * (flipped ? -1 : 1); + const auto ratioH = size.height() / float64(rotatedImageSize.height()); + + _view->setTransform(QTransform().scale(ratioW, ratioH).rotate(angle)); + _view->setGeometry(QRect(QPoint(), size)); +} + +void Paint::initDrawing() { + using Result = base::EventFilterResult; + + _brushData.size = 10; + _brushData.color = Qt::red; + + auto callback = [=](not_null event) { + const auto type = event->type(); + const auto isPress = (type == QEvent::GraphicsSceneMousePress); + const auto isMove = (type == QEvent::GraphicsSceneMouseMove); + const auto isRelease = (type == QEvent::GraphicsSceneMouseRelease); + if (!isPress && !isMove && !isRelease) { + return Result::Continue; + } + const auto e = static_cast(event.get()); + + const auto &size = _brushData.size; + const auto &color = _brushData.color; + const auto mousePoint = e->scenePos(); + if (isPress) { + auto dot = _scene->addEllipse( + mousePoint.x() - size / 2, + mousePoint.y() - size / 2, + size, + size, + QPen(Qt::NoPen), + QBrush(color)); + _brushData.group = _scene->createItemGroup( + QList{ std::move(dot) }); + } + if (isMove && _brushData.group) { + _brushData.group->addToGroup(_scene->addLine( + _brushData.lastPoint.x(), + _brushData.lastPoint.y(), + mousePoint.x(), + mousePoint.y(), + QPen(color, size, Qt::SolidLine, Qt::RoundCap))); + } + if (isRelease) { + _brushData.group = nullptr; + } + _brushData.lastPoint = mousePoint; + + return Result::Cancel; + }; + + base::install_event_filter(this, _scene.get(), std::move(callback)); +} + +void Paint::applyMode(PhotoEditorMode mode) { + setAttribute( + Qt::WA_TransparentForMouseEvents, + mode == PhotoEditorMode::Transform); +} + +std::shared_ptr Paint::saveScene() const { + return _scene; +} + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/editor_paint.h b/Telegram/SourceFiles/editor/editor_paint.h new file mode 100644 index 000000000..20ea30783 --- /dev/null +++ b/Telegram/SourceFiles/editor/editor_paint.h @@ -0,0 +1,49 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +#include "ui/rp_widget.h" + +#include "editor/photo_editor_common.h" + +class QGraphicsItemGroup; +class QGraphicsView; + +namespace Editor { + +// Paint control. +class Paint final : public Ui::RpWidget { +public: + Paint( + not_null parent, + PhotoModifications &modifications, + const QSize &imageSize); + + [[nodiscard]] std::shared_ptr saveScene() const; + + void applyTransform(QRect geometry, int angle, bool flipped); + void applyMode(PhotoEditorMode mode); + +private: + void initDrawing(); + + const std::shared_ptr _scene; + const base::unique_qptr _view; + const QSize _imageSize; + + struct { + QPointF lastPoint; + + float size = 1.; + QColor color; + QGraphicsItemGroup *group; + } _brushData; + +}; + +} // namespace Editor diff --git a/Telegram/SourceFiles/editor/photo_editor.cpp b/Telegram/SourceFiles/editor/photo_editor.cpp index f83fef724..75fc2732a 100644 --- a/Telegram/SourceFiles/editor/photo_editor.cpp +++ b/Telegram/SourceFiles/editor/photo_editor.cpp @@ -18,11 +18,11 @@ PhotoEditor::PhotoEditor( std::shared_ptr photo, PhotoModifications modifications) : RpWidget(parent) -, _modifications(modifications) +, _modifications(std::move(modifications)) , _content(base::make_unique_q( this, photo, - modifications)) + _modifications)) , _controls(base::make_unique_q(this)) { sizeValue( ) | rpl::start_with_next([=](const QSize &size) { @@ -49,10 +49,16 @@ PhotoEditor::PhotoEditor( _modifications.flipped = !_modifications.flipped; _content->applyModifications(_modifications); }, lifetime()); + + _controls->paintModeRequests( + ) | rpl::start_with_next([=] { + _content->applyMode(PhotoEditorMode::Paint); + }, lifetime()); + _content->applyMode(PhotoEditorMode::Transform); } void PhotoEditor::save() { - _modifications.crop = _content->cropRect(); + _content->save(_modifications); _done.fire_copy(_modifications); } diff --git a/Telegram/SourceFiles/editor/photo_editor_common.cpp b/Telegram/SourceFiles/editor/photo_editor_common.cpp index b06d2aadc..981d224a9 100644 --- a/Telegram/SourceFiles/editor/photo_editor_common.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_common.cpp @@ -13,6 +13,12 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) { if (!mods) { return image; } + if (mods.paint) { + Painter p(&image); + PainterHighQualityEnabler hq(p); + + mods.paint->render(&p, image.rect()); + } QTransform transform; if (mods.flipped) { transform.scale(-1, 1); @@ -27,4 +33,18 @@ QImage ImageModified(QImage image, const PhotoModifications &mods) { return newImage; } +bool PhotoModifications::empty() const { + return !angle && !flipped && !crop.isValid(); +} + +PhotoModifications::operator bool() const { + return !empty(); +} + +PhotoModifications::~PhotoModifications() { + if (paint && (paint.use_count() == 1)) { + paint->deleteLater(); + } +} + } // namespace Editor diff --git a/Telegram/SourceFiles/editor/photo_editor_common.h b/Telegram/SourceFiles/editor/photo_editor_common.h index 517ca24a2..6dc1b7b31 100644 --- a/Telegram/SourceFiles/editor/photo_editor_common.h +++ b/Telegram/SourceFiles/editor/photo_editor_common.h @@ -7,19 +7,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL */ #pragma once +#include + namespace Editor { +enum class PhotoEditorMode { + Transform, + Paint, +}; + struct PhotoModifications { int angle = 0; bool flipped = false; QRect crop; + std::shared_ptr paint = nullptr; - [[nodiscard]] bool empty() const { - return !angle && !flipped && !crop.isValid(); - } - [[nodiscard]] explicit operator bool() const { - return !empty(); - } + [[nodiscard]] bool empty() const; + [[nodiscard]] explicit operator bool() const; + ~PhotoModifications(); }; diff --git a/Telegram/SourceFiles/editor/photo_editor_content.cpp b/Telegram/SourceFiles/editor/photo_editor_content.cpp index 96460c5ad..45b81137b 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_content.cpp @@ -8,6 +8,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/photo_editor_content.h" #include "editor/editor_crop.h" +#include "editor/editor_paint.h" #include "media/view/media_view_pip.h" namespace Editor { @@ -20,6 +21,7 @@ PhotoEditorContent::PhotoEditorContent( std::shared_ptr photo, PhotoModifications modifications) : RpWidget(parent) +, _paint(base::make_unique_q(this, modifications, photo->size())) , _crop(base::make_unique_q(this, modifications, photo->size())) , _photo(photo) , _modifications(modifications) { @@ -58,10 +60,12 @@ PhotoEditorContent::PhotoEditorContent( } _imageMatrix.rotate(mods.angle); + const auto geometry = _imageMatrix.mapRect(_imageRect); _crop->applyTransform( - _imageMatrix.mapRect(_imageRect) + _crop->cropMargins(), + geometry + _crop->cropMargins(), mods.angle, mods.flipped); + _paint->applyTransform(geometry, mods.angle, mods.flipped); }, lifetime()); paintRequest( @@ -82,8 +86,17 @@ void PhotoEditorContent::applyModifications( update(); } -QRect PhotoEditorContent::cropRect() const { - return _crop->saveCropRect(_imageRect, _photo->rect()); +void PhotoEditorContent::save(PhotoModifications &modifications) { + modifications.crop = _crop->saveCropRect(_imageRect, _photo->rect()); + if (!modifications.paint) { + modifications.paint = _paint->saveScene(); + } +} + +void PhotoEditorContent::applyMode(PhotoEditorMode mode) { + _crop->setVisible(mode == PhotoEditorMode::Transform); + _paint->applyMode(mode); + _mode = mode; } } // namespace Editor diff --git a/Telegram/SourceFiles/editor/photo_editor_content.h b/Telegram/SourceFiles/editor/photo_editor_content.h index 7ccd3acf9..8fa5cc6ec 100644 --- a/Telegram/SourceFiles/editor/photo_editor_content.h +++ b/Telegram/SourceFiles/editor/photo_editor_content.h @@ -14,6 +14,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL namespace Editor { class Crop; +class Paint; class PhotoEditorContent final : public Ui::RpWidget { public: @@ -23,10 +24,12 @@ public: PhotoModifications modifications); void applyModifications(PhotoModifications modifications); - [[nodiscard]] QRect cropRect() const; + void applyMode(PhotoEditorMode mode); + void save(PhotoModifications &modifications); private: + const base::unique_qptr _paint; const base::unique_qptr _crop; const std::shared_ptr _photo; @@ -34,6 +37,7 @@ private: QRect _imageRect; QMatrix _imageMatrix; + PhotoEditorMode _mode; }; diff --git a/Telegram/SourceFiles/editor/photo_editor_controls.cpp b/Telegram/SourceFiles/editor/photo_editor_controls.cpp index 79649e6a0..562339aaa 100644 --- a/Telegram/SourceFiles/editor/photo_editor_controls.cpp +++ b/Telegram/SourceFiles/editor/photo_editor_controls.cpp @@ -48,6 +48,9 @@ PhotoEditorControls::PhotoEditorControls( _buttonsContainer, st::photoEditorRotateButton)) , _flipButton(base::make_unique_q( + _buttonsContainer, + st::photoEditorFlipButton)) +, _paintModeButton(base::make_unique_q( _buttonsContainer, st::photoEditorFlipButton)) { @@ -84,4 +87,8 @@ rpl::producer<> PhotoEditorControls::flipRequests() const { return _flipButton->clicks() | rpl::to_empty; } +rpl::producer<> PhotoEditorControls::paintModeRequests() const { + return _paintModeButton->clicks() | rpl::to_empty; +} + } // namespace Editor diff --git a/Telegram/SourceFiles/editor/photo_editor_controls.h b/Telegram/SourceFiles/editor/photo_editor_controls.h index 59308b6c7..e891b95cb 100644 --- a/Telegram/SourceFiles/editor/photo_editor_controls.h +++ b/Telegram/SourceFiles/editor/photo_editor_controls.h @@ -25,12 +25,14 @@ public: [[nodiscard]] rpl::producer rotateRequests() const; [[nodiscard]] rpl::producer<> flipRequests() const; + [[nodiscard]] rpl::producer<> paintModeRequests() const; private: const base::unique_qptr _buttonsContainer; const base::unique_qptr _rotateButton; const base::unique_qptr _flipButton; + const base::unique_qptr _paintModeButton; };