mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 14:17:12 +02:00
Slightly optimized mouse painting in photo editor.
This commit is contained in:
parent
690a7d1608
commit
a6904be81d
7 changed files with 253 additions and 96 deletions
|
@ -63,6 +63,11 @@ Paint::Paint(
|
|||
clearRedoList();
|
||||
}, lifetime());
|
||||
|
||||
_scene->addsItem(
|
||||
) | rpl::start_with_next([=] {
|
||||
updateUndoState();
|
||||
}, lifetime());
|
||||
|
||||
// Undo / Redo.
|
||||
controllers->undoController->performRequestChanges(
|
||||
) | rpl::start_with_next([=](const Undo &command) {
|
||||
|
|
|
@ -11,93 +11,75 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "editor/scene_item_line.h"
|
||||
#include "ui/rp_widget.h"
|
||||
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
|
||||
namespace Editor {
|
||||
namespace {
|
||||
|
||||
bool SkipMouseEvent(not_null<QGraphicsSceneMouseEvent*> event) {
|
||||
return event->isAccepted() || (event->button() == Qt::RightButton);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Scene::Scene(const QRectF &rect)
|
||||
: QGraphicsScene(rect)
|
||||
, _canvas(new ItemCanvas) {
|
||||
clearPath();
|
||||
|
||||
QGraphicsScene::addItem(_canvas);
|
||||
_canvas->clearPixmap();
|
||||
|
||||
_canvas->paintRequest(
|
||||
) | rpl::start_with_next([=](not_null<QPainter*> p) {
|
||||
p->fillRect(sceneRect(), Qt::transparent);
|
||||
|
||||
p->setPen(QPen(_brushData.color, _brushData.size));
|
||||
p->drawPath(_path);
|
||||
_canvas->grabContentRequests(
|
||||
) | rpl::start_with_next([=](ItemCanvas::Content &&content) {
|
||||
const auto item = new ItemLine(std::move(content.pixmap));
|
||||
item->setPos(content.position);
|
||||
addItem(item);
|
||||
_canvas->setZValue(++_lastLineZ);
|
||||
}, _lifetime);
|
||||
}
|
||||
|
||||
void Scene::addItem(not_null<NumberedItem*> item) {
|
||||
item->setNumber(_itemNumber++);
|
||||
QGraphicsScene::addItem(item);
|
||||
_addsItem.fire({});
|
||||
}
|
||||
|
||||
void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event) {
|
||||
QGraphicsScene::mousePressEvent(event);
|
||||
if (event->isAccepted() || (event->button() == Qt::RightButton)) {
|
||||
if (SkipMouseEvent(event)) {
|
||||
return;
|
||||
}
|
||||
_canvas->handleMousePressEvent(event);
|
||||
_mousePresses.fire({});
|
||||
_path.moveTo(event->scenePos());
|
||||
_drawing = true;
|
||||
}
|
||||
|
||||
void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
|
||||
QGraphicsScene::mouseReleaseEvent(event);
|
||||
if (event->isAccepted() || (event->button() == Qt::RightButton)) {
|
||||
if (SkipMouseEvent(event)) {
|
||||
return;
|
||||
}
|
||||
_path.lineTo(event->scenePos());
|
||||
addLineItem();
|
||||
_drawing = false;
|
||||
_canvas->handleMouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
|
||||
QGraphicsScene::mouseMoveEvent(event);
|
||||
if (event->isAccepted()
|
||||
|| (event->button() == Qt::RightButton)
|
||||
|| !_drawing) {
|
||||
if (SkipMouseEvent(event)) {
|
||||
return;
|
||||
}
|
||||
const auto scenePos = event->scenePos();
|
||||
_path.lineTo(scenePos);
|
||||
_path.moveTo(scenePos);
|
||||
_canvas->update();
|
||||
}
|
||||
|
||||
void Scene::addLineItem() {
|
||||
if (_path.capacity() < 3) {
|
||||
return;
|
||||
}
|
||||
addItem(new ItemLine(
|
||||
_path,
|
||||
sceneRect().size().toSize() * cIntRetinaFactor(),
|
||||
_brushData.color,
|
||||
_brushData.size));
|
||||
_canvas->setZValue(++_lastLineZ);
|
||||
clearPath();
|
||||
_canvas->handleMouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void Scene::applyBrush(const QColor &color, float size) {
|
||||
_brushData.color = color;
|
||||
_brushData.size = size;
|
||||
}
|
||||
|
||||
void Scene::clearPath() {
|
||||
_path = QPainterPath();
|
||||
_path.setFillRule(Qt::WindingFill);
|
||||
_canvas->applyBrush(color, size);
|
||||
}
|
||||
|
||||
rpl::producer<> Scene::mousePresses() const {
|
||||
return _mousePresses.events();
|
||||
}
|
||||
|
||||
rpl::producer<> Scene::addsItem() const {
|
||||
return _addsItem.events();
|
||||
}
|
||||
|
||||
std::vector<QGraphicsItem*> Scene::items(Qt::SortOrder order) const {
|
||||
using Item = QGraphicsItem;
|
||||
auto rawItems = QGraphicsScene::items();
|
||||
|
|
|
@ -11,8 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include <QGraphicsScene>
|
||||
|
||||
class QGraphicsSceneMouseEvent;
|
||||
class QGraphicsSceneMouseEvent;
|
||||
class QGraphicsSceneMouseEvent;
|
||||
|
||||
namespace Ui {
|
||||
|
@ -33,6 +31,7 @@ public:
|
|||
[[nodiscard]] std::vector<QGraphicsItem*> items(
|
||||
Qt::SortOrder order = Qt::DescendingOrder) const;
|
||||
void addItem(not_null<NumberedItem*> item);
|
||||
[[nodiscard]] rpl::producer<> addsItem() const;
|
||||
[[nodiscard]] rpl::producer<> mousePresses() const;
|
||||
|
||||
protected:
|
||||
|
@ -40,24 +39,13 @@ protected:
|
|||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
private:
|
||||
void clearPath();
|
||||
void addLineItem();
|
||||
|
||||
const not_null<ItemCanvas*> _canvas;
|
||||
|
||||
QPainterPath _path;
|
||||
bool _drawing = false;
|
||||
|
||||
float64 _lastLineZ = 0.;
|
||||
|
||||
int _itemNumber = 0;
|
||||
|
||||
struct {
|
||||
float size = 1.;
|
||||
QColor color;
|
||||
} _brushData;
|
||||
|
||||
rpl::event_stream<> _mousePresses;
|
||||
rpl::event_stream<> _addsItem;
|
||||
rpl::lifetime _lifetime;
|
||||
|
||||
};
|
||||
|
|
|
@ -8,32 +8,197 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include "editor/scene_item_canvas.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
|
||||
namespace Editor {
|
||||
namespace {
|
||||
|
||||
QRectF NormalizedRect(const QPointF& p1, const QPointF& p2) {
|
||||
return QRectF(
|
||||
std::min(p1.x(), p2.x()),
|
||||
std::min(p1.y(), p2.y()),
|
||||
std::abs(p2.x() - p1.x()) + 1,
|
||||
std::abs(p2.y() - p1.y()) + 1);
|
||||
}
|
||||
|
||||
std::vector<QPointF> InterpolatedPoints(
|
||||
const QPointF &startPoint,
|
||||
const QPointF &endPoint) {
|
||||
std::vector<QPointF> points;
|
||||
|
||||
const auto x1 = startPoint.x();
|
||||
const auto y1 = startPoint.y();
|
||||
const auto x2 = endPoint.x();
|
||||
const auto y2 = endPoint.y();
|
||||
|
||||
// Difference of x and y values.
|
||||
const auto dx = x2 - x1;
|
||||
const auto dy = y2 - y1;
|
||||
|
||||
// Absolute values of differences.
|
||||
const auto ix = std::abs(dx);
|
||||
const auto iy = std::abs(dy);
|
||||
|
||||
// Larger of the x and y differences.
|
||||
const auto inc = ix > iy ? ix : iy;
|
||||
|
||||
// Plot location.
|
||||
auto plotx = x1;
|
||||
auto ploty = y1;
|
||||
|
||||
auto x = 0;
|
||||
auto y = 0;
|
||||
|
||||
points.push_back(QPointF(plotx, ploty));
|
||||
|
||||
for (auto i = 0; i <= inc; i++) {
|
||||
const auto xInc = x > inc;
|
||||
const auto yInc = y > inc;
|
||||
|
||||
x += ix;
|
||||
y += iy;
|
||||
|
||||
if (xInc) {
|
||||
x -= inc;
|
||||
plotx += 1 * ((dx < 0) ? -1 : 1);
|
||||
}
|
||||
|
||||
if (yInc) {
|
||||
y -= inc;
|
||||
ploty += 1 * ((dy < 0) ? -1 : 1);
|
||||
}
|
||||
|
||||
if (xInc || yInc) {
|
||||
points.push_back(QPointF(plotx, ploty));
|
||||
}
|
||||
}
|
||||
return points;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemCanvas::ItemCanvas() {
|
||||
setAcceptedMouseButtons(0);
|
||||
}
|
||||
|
||||
void ItemCanvas::clearPixmap() {
|
||||
_hq = nullptr;
|
||||
_p = nullptr;
|
||||
|
||||
_pixmap = QPixmap(
|
||||
(scene()->sceneRect().size() * cIntRetinaFactor()).toSize());
|
||||
_pixmap.setDevicePixelRatio(cRetinaFactor());
|
||||
_pixmap.fill(Qt::transparent);
|
||||
|
||||
_p = std::make_unique<Painter>(&_pixmap);
|
||||
_hq = std::make_unique<PainterHighQualityEnabler>(*_p);
|
||||
_p->setPen(Qt::NoPen);
|
||||
_p->setBrush(_brushData.color);
|
||||
}
|
||||
|
||||
void ItemCanvas::applyBrush(const QColor &color, float size) {
|
||||
_brushData.color = color;
|
||||
_brushData.size = size;
|
||||
_p->setBrush(color);
|
||||
_brushMargins = QMarginsF(size, size, size, size) / 2.;
|
||||
}
|
||||
|
||||
QRectF ItemCanvas::boundingRect() const {
|
||||
return scene()->sceneRect();
|
||||
}
|
||||
|
||||
void ItemCanvas::computeContentRect(const QPointF &p) {
|
||||
if (!scene()) {
|
||||
return;
|
||||
}
|
||||
const auto sceneSize = scene()->sceneRect().size();
|
||||
_contentRect = QRectF(
|
||||
QPointF(
|
||||
std::clamp(p.x() - _brushMargins.left(), 0., _contentRect.x()),
|
||||
std::clamp(p.y() - _brushMargins.top(), 0., _contentRect.y())
|
||||
),
|
||||
QPointF(
|
||||
std::clamp(
|
||||
p.x() + _brushMargins.right(),
|
||||
_contentRect.x() + _contentRect.width(),
|
||||
sceneSize.width()),
|
||||
std::clamp(
|
||||
p.y() + _brushMargins.bottom(),
|
||||
_contentRect.y() + _contentRect.height(),
|
||||
sceneSize.height())
|
||||
));
|
||||
}
|
||||
|
||||
void ItemCanvas::drawLine(
|
||||
const QPointF ¤tPoint,
|
||||
const QPointF &lastPoint) {
|
||||
const auto halfBrushSize = _brushData.size / 2.;
|
||||
const auto points = InterpolatedPoints(lastPoint, currentPoint);
|
||||
|
||||
_rectToUpdate |= NormalizedRect(currentPoint, lastPoint) + _brushMargins;
|
||||
|
||||
for (const auto &point : points) {
|
||||
_p->drawEllipse(point, halfBrushSize, halfBrushSize);
|
||||
}
|
||||
}
|
||||
|
||||
void ItemCanvas::handleMousePressEvent(
|
||||
not_null<QGraphicsSceneMouseEvent*> e) {
|
||||
_lastPoint = e->scenePos();
|
||||
_contentRect = QRectF(_lastPoint, _lastPoint);
|
||||
_drawing = true;
|
||||
}
|
||||
|
||||
void ItemCanvas::handleMouseMoveEvent(
|
||||
not_null<QGraphicsSceneMouseEvent*> e) {
|
||||
if (!_drawing) {
|
||||
return;
|
||||
}
|
||||
const auto scenePos = e->scenePos();
|
||||
drawLine(scenePos, _lastPoint);
|
||||
update(_rectToUpdate);
|
||||
computeContentRect(scenePos);
|
||||
_lastPoint = scenePos;
|
||||
}
|
||||
|
||||
void ItemCanvas::handleMouseReleaseEvent(
|
||||
not_null<QGraphicsSceneMouseEvent*> e) {
|
||||
if (!_drawing) {
|
||||
return;
|
||||
}
|
||||
_drawing = false;
|
||||
|
||||
const auto scaledContentRect = QRectF(
|
||||
_contentRect.x() * cRetinaFactor(),
|
||||
_contentRect.y() * cRetinaFactor(),
|
||||
_contentRect.width() * cRetinaFactor(),
|
||||
_contentRect.height() * cRetinaFactor());
|
||||
|
||||
_grabContentRequests.fire({
|
||||
.pixmap = _pixmap.copy(scaledContentRect.toRect()),
|
||||
.position = _contentRect.topLeft(),
|
||||
});
|
||||
clearPixmap();
|
||||
update();
|
||||
}
|
||||
|
||||
void ItemCanvas::paint(
|
||||
QPainter *p,
|
||||
const QStyleOptionGraphicsItem *,
|
||||
QWidget *) {
|
||||
_paintRequest.fire_copy(p);
|
||||
p->fillRect(_rectToUpdate, Qt::transparent);
|
||||
p->drawPixmap(0, 0, _pixmap);
|
||||
_rectToUpdate = QRectF();
|
||||
}
|
||||
|
||||
rpl::producer<ItemCanvas::Content> ItemCanvas::grabContentRequests() const {
|
||||
return _grabContentRequests.events();
|
||||
}
|
||||
|
||||
int ItemCanvas::type() const {
|
||||
return Type;
|
||||
}
|
||||
|
||||
rpl::producer<not_null<QPainter*>> ItemCanvas::paintRequest() const {
|
||||
return _paintRequest.events();
|
||||
}
|
||||
|
||||
bool ItemCanvas::collidesWithItem(
|
||||
const QGraphicsItem *,
|
||||
Qt::ItemSelectionMode) const {
|
||||
|
@ -46,4 +211,9 @@ bool ItemCanvas::collidesWithPath(
|
|||
return false;
|
||||
}
|
||||
|
||||
ItemCanvas::~ItemCanvas() {
|
||||
_hq = nullptr;
|
||||
_p = nullptr;
|
||||
}
|
||||
|
||||
} // namespace Editor
|
||||
|
|
|
@ -9,13 +9,24 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
|
||||
#include <QGraphicsItem>
|
||||
|
||||
class QGraphicsSceneMouseEvent;
|
||||
|
||||
namespace Editor {
|
||||
|
||||
class ItemCanvas : public QGraphicsItem {
|
||||
public:
|
||||
enum { Type = UserType + 6 };
|
||||
|
||||
struct Content {
|
||||
QPixmap pixmap;
|
||||
QPointF position;
|
||||
};
|
||||
|
||||
ItemCanvas();
|
||||
~ItemCanvas();
|
||||
|
||||
void applyBrush(const QColor &color, float size);
|
||||
void clearPixmap();
|
||||
|
||||
QRectF boundingRect() const override;
|
||||
void paint(
|
||||
|
@ -24,7 +35,12 @@ public:
|
|||
QWidget *widget) override;
|
||||
int type() const override;
|
||||
|
||||
[[nodiscard]] rpl::producer<not_null<QPainter*>> paintRequest() const;
|
||||
void handleMousePressEvent(not_null<QGraphicsSceneMouseEvent*> event);
|
||||
void handleMouseReleaseEvent(not_null<QGraphicsSceneMouseEvent*> event);
|
||||
void handleMouseMoveEvent(not_null<QGraphicsSceneMouseEvent*> event);
|
||||
|
||||
[[nodiscard]] rpl::producer<Content> grabContentRequests() const;
|
||||
|
||||
protected:
|
||||
bool collidesWithItem(
|
||||
const QGraphicsItem *,
|
||||
|
@ -34,7 +50,28 @@ protected:
|
|||
const QPainterPath &,
|
||||
Qt::ItemSelectionMode) const override;
|
||||
private:
|
||||
rpl::event_stream<not_null<QPainter*>> _paintRequest;
|
||||
void computeContentRect(const QPointF &p);
|
||||
void drawLine(const QPointF ¤tPoint, const QPointF &lastPoint);
|
||||
|
||||
bool _drawing = false;
|
||||
|
||||
std::unique_ptr<PainterHighQualityEnabler> _hq;
|
||||
std::unique_ptr<Painter> _p;
|
||||
|
||||
QRectF _rectToUpdate;
|
||||
QRectF _contentRect;
|
||||
QMarginsF _brushMargins;
|
||||
|
||||
QPointF _lastPoint;
|
||||
|
||||
QPixmap _pixmap;
|
||||
|
||||
struct {
|
||||
float size = 1.;
|
||||
QColor color;
|
||||
} _brushData;
|
||||
|
||||
rpl::event_stream<Content> _grabContentRequests;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -10,35 +10,14 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
|||
#include <QGraphicsScene>
|
||||
|
||||
namespace Editor {
|
||||
namespace {
|
||||
|
||||
QPixmap PathToPixmap(
|
||||
const QPainterPath &path,
|
||||
const QSize &size,
|
||||
const QColor &brushColor,
|
||||
float brushSize) {
|
||||
auto pixmap = QPixmap(size);
|
||||
pixmap.setDevicePixelRatio(cRetinaFactor());
|
||||
pixmap.fill(Qt::transparent);
|
||||
Painter p(&pixmap);
|
||||
p.setPen(QPen(brushColor, brushSize));
|
||||
p.drawPath(path);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ItemLine::ItemLine(
|
||||
const QPainterPath &path,
|
||||
const QSize &size,
|
||||
const QColor &brushColor,
|
||||
float brushSize)
|
||||
: _pixmap(PathToPixmap(path, size, brushColor, brushSize)) {
|
||||
Expects(path.capacity() > 0);
|
||||
ItemLine::ItemLine(const QPixmap &&pixmap)
|
||||
: _pixmap(std::move(pixmap))
|
||||
, _rect(QPointF(), _pixmap.size() / cRetinaFactor()) {
|
||||
}
|
||||
|
||||
QRectF ItemLine::boundingRect() const {
|
||||
return scene()->sceneRect();
|
||||
return _rect;
|
||||
}
|
||||
|
||||
void ItemLine::paint(
|
||||
|
|
|
@ -15,11 +15,7 @@ class ItemLine : public NumberedItem {
|
|||
public:
|
||||
enum { Type = UserType + 5 };
|
||||
|
||||
ItemLine(
|
||||
const QPainterPath &path,
|
||||
const QSize &size,
|
||||
const QColor &brushColor,
|
||||
float brushSize);
|
||||
ItemLine(const QPixmap &&pixmap);
|
||||
QRectF boundingRect() const override;
|
||||
void paint(
|
||||
QPainter *p,
|
||||
|
@ -33,8 +29,8 @@ protected:
|
|||
const QPainterPath &,
|
||||
Qt::ItemSelectionMode) const override;
|
||||
private:
|
||||
const QRectF _rect;
|
||||
const QPixmap _pixmap;
|
||||
const QRectF _rect;
|
||||
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue