diff --git a/Telegram/SourceFiles/editor/editor.style b/Telegram/SourceFiles/editor/editor.style index babe843e10..354564324f 100644 --- a/Telegram/SourceFiles/editor/editor.style +++ b/Telegram/SourceFiles/editor/editor.style @@ -110,6 +110,7 @@ photoEditorMarkerSizeMultiplier: 2.5; photoEditorArrowHeadLengthFactor: 2.5; photoEditorArrowHeadMinDistanceFactor: 1.5; photoEditorArrowHeadAngleDegrees: 26; +photoEditorEraserPreviewOpacity: 0.25; photoEditorBrushSizeControlLeftSkip: 0px; photoEditorBrushSizeControlHeight: 280px; diff --git a/Telegram/SourceFiles/editor/editor_paint.cpp b/Telegram/SourceFiles/editor/editor_paint.cpp index 61f78964c6..679de0d842 100644 --- a/Telegram/SourceFiles/editor/editor_paint.cpp +++ b/Telegram/SourceFiles/editor/editor_paint.cpp @@ -68,7 +68,10 @@ Paint::Paint( _view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _view->setFrameStyle(int(QFrame::NoFrame) | QFrame::Plain); + _view->setBackgroundBrush(Qt::transparent); + _view->setAttribute(Qt::WA_TranslucentBackground, true); _viewport->setAutoFillBackground(false); + _viewport->setAttribute(Qt::WA_TranslucentBackground, true); _viewport->installEventFilter(this); // Undo / Redo. diff --git a/Telegram/SourceFiles/editor/scene/scene.cpp b/Telegram/SourceFiles/editor/scene/scene.cpp index 924d971536..c141c3e002 100644 --- a/Telegram/SourceFiles/editor/scene/scene.cpp +++ b/Telegram/SourceFiles/editor/scene/scene.cpp @@ -19,6 +19,81 @@ namespace { using ItemPtr = std::shared_ptr; +class ItemEraser final : public NumberedItem { +public: + struct Target { + std::shared_ptr item; + QPixmap before; + }; + + ItemEraser( + QPixmap mask, + QPointF maskPos, + std::vector targets) + : _mask(std::move(mask)) + , _maskPos(maskPos) + , _targets(std::move(targets)) { + } + + void apply() { + for (const auto &target : _targets) { + target.item->applyEraser(_mask, _maskPos); + } + } + + void revert() { + for (const auto &target : _targets) { + target.item->setPixmap(target.before); + } + } + + QRectF boundingRect() const override { + return QRectF(); + } + + void paint( + QPainter *, + const QStyleOptionGraphicsItem *, + QWidget *) override { + } + + bool hasState(SaveState state) const override { + const auto &saved = (state == SaveState::Keep) ? _keeped : _saved; + return saved.saved; + } + + void save(SaveState state) override { + auto &saved = (state == SaveState::Keep) ? _keeped : _saved; + saved = { + .saved = true, + .status = status(), + }; + } + + void restore(SaveState state) override { + if (!hasState(state)) { + return; + } + const auto &saved = (state == SaveState::Keep) ? _keeped : _saved; + setStatus(saved.status); + if (saved.status == Status::Normal) { + apply(); + } else if (saved.status == Status::Undid) { + revert(); + } + } + +private: + QPixmap _mask; + QPointF _maskPos; + std::vector _targets; + + struct { + bool saved = false; + NumberedItem::Status status; + } _saved, _keeped; +}; + bool SkipMouseEvent(not_null event) { return event->isAccepted() || (event->button() == Qt::RightButton); } @@ -34,9 +109,54 @@ Scene::Scene(const QRectF &rect) _canvas->grabContentRequests( ) | rpl::on_next([=](ItemCanvas::Content &&content) { + if (content.clear) { + auto mask = std::move(content.pixmap); + if (mask.isNull()) { + return; + } + const auto maskPos = content.position; + const auto maskSize = mask.size() + / float64(mask.devicePixelRatio()); + const auto maskRect = QRectF(maskPos, maskSize); + auto targets = std::vector(); + const auto hits = QGraphicsScene::items( + maskRect, + Qt::IntersectsItemBoundingRect, + Qt::DescendingOrder); + for (auto *raw : hits) { + const auto it = _itemsByPointer.find(raw); + if (it == end(_itemsByPointer)) { + continue; + } + const auto &item = it->second; + if (!item->isNormalStatus()) { + continue; + } + const auto line = std::dynamic_pointer_cast(item); + if (!line) { + continue; + } + auto before = line->pixmap(); + if (!line->applyEraser(mask, maskPos)) { + continue; + } + targets.push_back({ + .item = line, + .before = std::move(before), + }); + } + if (!targets.empty()) { + const auto eraser = std::make_shared( + std::move(mask), + maskPos, + std::move(targets)); + addItem(eraser); + _canvas->setZValue(++_lastLineZ); + } + return; + } const auto item = std::make_shared( - std::move(content.pixmap), - content.clear); + std::move(content.pixmap)); item->setPos(content.position); addItem(item); _canvas->setZValue(++_lastLineZ); @@ -53,7 +173,9 @@ void Scene::addItem(ItemPtr item) { } item->setNumber(_itemNumber++); QGraphicsScene::addItem(item.get()); + const auto raw = item.get(); _items.push_back(std::move(item)); + _itemsByPointer.emplace(raw, _items.back()); _addsItem.fire({}); } @@ -147,6 +269,9 @@ void Scene::performUndo() { const auto it = ranges::find_if(filtered, &NumberedItem::isNormalStatus); if (it != filtered.end()) { + if (const auto eraser = dynamic_cast(it->get())) { + eraser->revert(); + } (*it)->setStatus(NumberedItem::Status::Undid); } } @@ -156,6 +281,9 @@ void Scene::performRedo() { const auto it = ranges::find_if(filtered, &NumberedItem::isUndidStatus); if (it != filtered.end()) { + if (const auto eraser = dynamic_cast(it->get())) { + eraser->apply(); + } (*it)->setStatus(NumberedItem::Status::Normal); } } @@ -173,6 +301,10 @@ void Scene::removeIf(Fn proj) { } } _items = std::move(copy); + _itemsByPointer.clear(); + for (const auto &item : _items) { + _itemsByPointer.emplace(item.get(), item); + } } void Scene::clearRedoList() { diff --git a/Telegram/SourceFiles/editor/scene/scene.h b/Telegram/SourceFiles/editor/scene/scene.h index f389b72d9c..2077e8792d 100644 --- a/Telegram/SourceFiles/editor/scene/scene.h +++ b/Telegram/SourceFiles/editor/scene/scene.h @@ -65,6 +65,7 @@ private: const std::shared_ptr _lastZ; std::vector _items; + std::unordered_map _itemsByPointer; float64 _lastLineZ = 0.; int _itemNumber = 0; diff --git a/Telegram/SourceFiles/editor/scene/scene_item_canvas.cpp b/Telegram/SourceFiles/editor/scene/scene_item_canvas.cpp index 8ee569a1c9..0cd1a619d7 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_canvas.cpp +++ b/Telegram/SourceFiles/editor/scene/scene_item_canvas.cpp @@ -382,7 +382,7 @@ void ItemCanvas::paint( p->fillRect(_rectToUpdate, Qt::transparent); if (_brushData.tool == Brush::Tool::Eraser) { p->save(); - p->setCompositionMode(QPainter::CompositionMode_Clear); + p->setOpacity(st::photoEditorEraserPreviewOpacity); p->drawPixmap(0, 0, _pixmap); p->restore(); } else { diff --git a/Telegram/SourceFiles/editor/scene/scene_item_line.cpp b/Telegram/SourceFiles/editor/scene/scene_item_line.cpp index 2f06b86953..ca608fc561 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_line.cpp +++ b/Telegram/SourceFiles/editor/scene/scene_item_line.cpp @@ -8,13 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "editor/scene/scene_item_line.h" #include +#include namespace Editor { -ItemLine::ItemLine(QPixmap &&pixmap, bool clear) +ItemLine::ItemLine(QPixmap &&pixmap) : _pixmap(std::move(pixmap)) -, _rect(QPointF(), _pixmap.size() / float64(style::DevicePixelRatio())) -, _clear(clear) { +, _rect(QPointF(), _pixmap.size() / float64(style::DevicePixelRatio())) { } QRectF ItemLine::boundingRect() const { @@ -25,16 +25,39 @@ void ItemLine::paint( QPainter *p, const QStyleOptionGraphicsItem *, QWidget *) { - if (_clear) { - p->save(); - p->setCompositionMode(QPainter::CompositionMode_Clear); - p->drawPixmap(0, 0, _pixmap); - p->restore(); - return; - } p->drawPixmap(0, 0, _pixmap); } +const QPixmap &ItemLine::pixmap() const { + return _pixmap; +} + +void ItemLine::setPixmap(QPixmap pixmap) { + _pixmap = std::move(pixmap); + update(); +} + +bool ItemLine::applyEraser(const QPixmap &mask, const QPointF &maskPos) { + if (mask.isNull()) { + return false; + } + const auto maskSize = mask.size() / float64(mask.devicePixelRatio()); + const auto localTopLeft = maskPos - pos(); + const auto localRect = QRectF(localTopLeft, maskSize); + if (!localRect.intersects(_rect)) { + return false; + } + auto image = _pixmap.toImage().convertToFormat( + QImage::Format_ARGB32_Premultiplied); + auto p = QPainter(&image); + p.setCompositionMode(QPainter::CompositionMode_DestinationOut); + p.drawPixmap(localTopLeft, mask); + p.end(); + _pixmap = QPixmap::fromImage(std::move(image)); + update(); + return true; +} + bool ItemLine::collidesWithItem( const QGraphicsItem *, Qt::ItemSelectionMode) const { diff --git a/Telegram/SourceFiles/editor/scene/scene_item_line.h b/Telegram/SourceFiles/editor/scene/scene_item_line.h index 8088ecb382..bd88150cf4 100644 --- a/Telegram/SourceFiles/editor/scene/scene_item_line.h +++ b/Telegram/SourceFiles/editor/scene/scene_item_line.h @@ -13,12 +13,15 @@ namespace Editor { class ItemLine : public NumberedItem { public: - ItemLine(QPixmap &&pixmap, bool clear = false); + ItemLine(QPixmap &&pixmap); QRectF boundingRect() const override; void paint( QPainter *p, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + [[nodiscard]] const QPixmap &pixmap() const; + void setPixmap(QPixmap pixmap); + bool applyEraser(const QPixmap &mask, const QPointF &maskPos); bool hasState(SaveState state) const override; void save(SaveState state) override; @@ -31,9 +34,8 @@ protected: const QPainterPath &, Qt::ItemSelectionMode) const override; private: - const QPixmap _pixmap; + QPixmap _pixmap; const QRectF _rect; - const bool _clear = false; struct { bool saved = false;