mirror of
https://github.com/Qz3rK/tdesktop.git
synced 2026-07-03 14:15:08 +02:00
[img-editor] Refined eraser brush algorithm.
This commit is contained in:
@@ -110,6 +110,7 @@ photoEditorMarkerSizeMultiplier: 2.5;
|
||||
photoEditorArrowHeadLengthFactor: 2.5;
|
||||
photoEditorArrowHeadMinDistanceFactor: 1.5;
|
||||
photoEditorArrowHeadAngleDegrees: 26;
|
||||
photoEditorEraserPreviewOpacity: 0.25;
|
||||
|
||||
photoEditorBrushSizeControlLeftSkip: 0px;
|
||||
photoEditorBrushSizeControlHeight: 280px;
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -19,6 +19,81 @@ namespace {
|
||||
|
||||
using ItemPtr = std::shared_ptr<NumberedItem>;
|
||||
|
||||
class ItemEraser final : public NumberedItem {
|
||||
public:
|
||||
struct Target {
|
||||
std::shared_ptr<ItemLine> item;
|
||||
QPixmap before;
|
||||
};
|
||||
|
||||
ItemEraser(
|
||||
QPixmap mask,
|
||||
QPointF maskPos,
|
||||
std::vector<Target> 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<Target> _targets;
|
||||
|
||||
struct {
|
||||
bool saved = false;
|
||||
NumberedItem::Status status;
|
||||
} _saved, _keeped;
|
||||
};
|
||||
|
||||
bool SkipMouseEvent(not_null<QGraphicsSceneMouseEvent*> 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<ItemEraser::Target>();
|
||||
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<ItemLine>(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<ItemEraser>(
|
||||
std::move(mask),
|
||||
maskPos,
|
||||
std::move(targets));
|
||||
addItem(eraser);
|
||||
_canvas->setZValue(++_lastLineZ);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const auto item = std::make_shared<ItemLine>(
|
||||
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<ItemEraser*>(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<ItemEraser*>(it->get())) {
|
||||
eraser->apply();
|
||||
}
|
||||
(*it)->setStatus(NumberedItem::Status::Normal);
|
||||
}
|
||||
}
|
||||
@@ -173,6 +301,10 @@ void Scene::removeIf(Fn<bool(const ItemPtr &)> proj) {
|
||||
}
|
||||
}
|
||||
_items = std::move(copy);
|
||||
_itemsByPointer.clear();
|
||||
for (const auto &item : _items) {
|
||||
_itemsByPointer.emplace(item.get(), item);
|
||||
}
|
||||
}
|
||||
|
||||
void Scene::clearRedoList() {
|
||||
|
||||
@@ -65,6 +65,7 @@ private:
|
||||
const std::shared_ptr<float64> _lastZ;
|
||||
|
||||
std::vector<ItemPtr> _items;
|
||||
std::unordered_map<QGraphicsItem*, ItemPtr> _itemsByPointer;
|
||||
|
||||
float64 _lastLineZ = 0.;
|
||||
int _itemNumber = 0;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,13 +8,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "editor/scene/scene_item_line.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QtGui/QPainter>
|
||||
|
||||
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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user