/* 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/scene/scene.h" #include "editor/scene/scene_item_canvas.h" #include "editor/scene/scene_item_line.h" #include "editor/scene/scene_item_sticker.h" #include "ui/image/image_prepare.h" #include "ui/rp_widget.h" #include "styles/style_editor.h" #include namespace Editor { 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); } } // namespace Scene::Scene(const QRectF &rect) : QGraphicsScene(rect) , _canvas(std::make_shared()) , _lastZ(std::make_shared(9000.)) { QGraphicsScene::addItem(_canvas.get()); _canvas->clearPixmap(); _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; } if (content.blur) { auto mask = std::move(content.pixmap); if (mask.isNull() || !_blurSource) { return; } const auto maskPos = content.position; const auto maskSize = mask.size() / float64(mask.devicePixelRatio()); const auto sourceRect = QRectF(maskPos, maskSize); const auto expandedRect = sourceRect.toAlignedRect().adjusted( -st::photoEditorBlurRadius, -st::photoEditorBlurRadius, st::photoEditorBlurRadius, st::photoEditorBlurRadius); const auto captureRect = expandedRect.intersected( sceneRect().toAlignedRect()); if (captureRect.isEmpty()) { return; } auto source = _blurSource(captureRect); if (source.isNull()) { return; } const auto sourceDpr = source.devicePixelRatio(); if (source.format() != QImage::Format_ARGB32_Premultiplied) { source = source.convertToFormat( QImage::Format_ARGB32_Premultiplied); source.setDevicePixelRatio(sourceDpr); } const auto canvasVisible = _canvas->isVisible(); _canvas->setVisible(false); { auto p = QPainter(&source); render( &p, QRectF(QPointF(), QSizeF(captureRect.size())), QRectF(captureRect), Qt::IgnoreAspectRatio); } _canvas->setVisible(canvasVisible); auto blurred = Images::BlurLargeImage( std::move(source), st::photoEditorBlurRadius); if (blurred.isNull()) { return; } blurred.setDevicePixelRatio(sourceDpr); auto result = QImage( mask.size(), QImage::Format_ARGB32_Premultiplied); result.setDevicePixelRatio(mask.devicePixelRatio()); result.fill(Qt::transparent); { auto p = QPainter(&result); p.drawImage( QRectF(QPointF(), maskSize), blurred, QRectF( sourceRect.x() - captureRect.x(), sourceRect.y() - captureRect.y(), sourceRect.width(), sourceRect.height())); p.setCompositionMode( QPainter::CompositionMode_DestinationIn); p.drawPixmap(0, 0, mask); } auto blurPixmap = QPixmap::fromImage(std::move(result)); const auto item = std::make_shared( std::move(blurPixmap)); item->setPos(maskPos); addItem(item); _canvas->setZValue(++_lastLineZ); return; } const auto item = std::make_shared( std::move(content.pixmap)); item->setPos(content.position); addItem(item); _canvas->setZValue(++_lastLineZ); }, _lifetime); } void Scene::cancelDrawing() { _canvas->cancelDrawing(); } void Scene::addItem(ItemPtr item) { if (!item) { return; } 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({}); } void Scene::removeItem(not_null item) { const auto it = ranges::find_if(_items, [&](const ItemPtr &i) { return i.get() == item; }); if (it == end(_items)) { return; } removeItem(*it); } void Scene::removeItem(const ItemPtr &item) { item->setStatus(NumberedItem::Status::Removed); _removesItem.fire({}); } void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mousePressEvent(event); if (SkipMouseEvent(event) || !sceneRect().contains(event->scenePos())) { return; } _canvas->handleMousePressEvent(event); } void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseReleaseEvent(event); if (SkipMouseEvent(event)) { return; } _canvas->handleMouseReleaseEvent(event); } void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QGraphicsScene::mouseMoveEvent(event); if (SkipMouseEvent(event)) { return; } _canvas->handleMouseMoveEvent(event); } void Scene::applyBrush(const QColor &color, float size, Brush::Tool tool) { _canvas->applyBrush(color, size, tool); } void Scene::setBlurSource(Fn source) { _blurSource = std::move(source); } rpl::producer<> Scene::addsItem() const { return _addsItem.events(); } rpl::producer<> Scene::removesItem() const { return _removesItem.events(); } std::vector Scene::items( Qt::SortOrder order) const { auto copyItems = _items; ranges::sort(copyItems, [&](ItemPtr a, ItemPtr b) { const auto numA = a->number(); const auto numB = b->number(); return (order == Qt::AscendingOrder) ? (numA < numB) : (numA > numB); }); return copyItems; } std::shared_ptr Scene::lastZ() const { return _lastZ; } void Scene::updateZoom(float64 zoom) { _canvas->updateZoom(zoom); for (const auto &item : items()) { if (item->type() >= ItemBase::Type) { static_cast(item.get())->updateZoom(zoom); } } } bool Scene::hasUndo() const { return ranges::any_of(_items, &NumberedItem::isNormalStatus); } bool Scene::hasRedo() const { return ranges::any_of(_items, &NumberedItem::isUndidStatus); } void Scene::performUndo() { const auto filtered = items(Qt::DescendingOrder); 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); } } void Scene::performRedo() { const auto filtered = items(Qt::AscendingOrder); 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); } } void Scene::removeIf(Fn proj) { auto copy = std::vector(); for (const auto &item : _items) { const auto toRemove = proj(item); if (toRemove) { // Scene loses ownership of an item. // It seems for some reason this line causes a crash. =( // QGraphicsScene::removeItem(item.get()); } else { copy.push_back(item); } } _items = std::move(copy); _itemsByPointer.clear(); for (const auto &item : _items) { _itemsByPointer.emplace(item.get(), item); } } void Scene::clearRedoList() { for (const auto &item : _items) { if (item->isUndidStatus()) { item->setStatus(NumberedItem::Status::Removed); } } } void Scene::save(SaveState state) { removeIf([](const ItemPtr &item) { return item->isRemovedStatus() && !item->hasState(SaveState::Keep) && !item->hasState(SaveState::Save); }); for (const auto &item : _items) { item->save(state); } clearSelection(); cancelDrawing(); } void Scene::restore(SaveState state) { removeIf([=](const ItemPtr &item) { return !item->hasState(state); }); for (const auto &item : _items) { item->restore(state); } clearSelection(); cancelDrawing(); } Scene::~Scene() { // Prevent destroying by scene of all items. QGraphicsScene::removeItem(_canvas.get()); for (const auto &item : items()) { // Scene loses ownership of an item. QGraphicsScene::removeItem(item.get()); } } } // namespace Editor