Slightly optimized mouse painting in photo editor.

This commit is contained in:
23rd 2021-03-08 19:31:48 +03:00
parent 690a7d1608
commit a6904be81d
7 changed files with 253 additions and 96 deletions

View File

@ -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) {

View File

@ -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();

View File

@ -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;
};

View File

@ -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 &currentPoint,
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

View File

@ -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 &currentPoint, 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;
};

View File

@ -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(

View File

@ -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;
};