removed QTransform and QPainterPath

This commit is contained in:
a.pavlov 2021-07-16 13:51:19 +03:00 committed by Igor Korsukov
parent e7a56c21e4
commit f322676d4d
33 changed files with 2109 additions and 114 deletions

View file

@ -214,7 +214,7 @@ void ExampleView::paintEvent(QPaintEvent* ev)
drawBackground(&p, r);
p.setWorldTransform(_matrix);
p.setWorldTransform(mu::Transform::fromQTransform(_matrix));
QRectF fr = imatrix.mapRect(r.toQRectF());
QRegion r1(r.toQRect());

View file

@ -0,0 +1,106 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "bezier.h"
namespace mu {
Bezier::Bezier(double x1, double y1,
double x2, double y2,
double x3, double y3,
double x4, double y4)
: m_x1(x1), m_y1(y1), m_x2(x2), m_y2(y2),
m_x3(x3), m_y3(y3), m_x4(x4), m_y4(y4)
{
}
Bezier Bezier::fromPoints(const PointF& p1, const PointF& p2,
const PointF& p3, const PointF& p4)
{
return { p1.x(), p1.y(), p2.x(), p2.y(), p3.x(), p3.y(), p4.x(), p4.y() };
}
void Bezier::coefficients(double t, double& a, double& b, double& c, double& d)
{
double m_t = 1. - t;
b = m_t * m_t;
c = t * t;
d = c * t;
a = b * m_t;
b *= 3. * t;
c *= 3. * m_t;
}
Bezier Bezier::bezierOnInterval(double t0, double t1) const
{
if (t0 == 0 && t1 == 1) {
return *this;
}
Bezier bezier = *this;
Bezier result;
bezier.parameterSplitLeft(t0, &result);
double trueT = (t1 - t0) / (1 - t0);
bezier.parameterSplitLeft(trueT, &result);
return result;
}
void Bezier::parameterSplitLeft(double t, Bezier* left)
{
left->m_x1 = m_x1;
left->m_y1 = m_y1;
left->m_x2 = m_x1 + t * (m_x2 - m_x1);
left->m_y2 = m_y1 + t * (m_y2 - m_y1);
left->m_x3 = m_x2 + t * (m_x3 - m_x2); // temporary holding spot
left->m_y3 = m_y2 + t * (m_y3 - m_y2); // temporary holding spot
m_x3 = m_x3 + t * (m_x4 - m_x3);
m_y3 = m_y3 + t * (m_y4 - m_y3);
m_x2 = left->m_x3 + t * (m_x3 - left->m_x3);
m_y2 = left->m_y3 + t * (m_y3 - left->m_y3);
left->m_x3 = left->m_x2 + t * (left->m_x3 - left->m_x2);
left->m_y3 = left->m_y2 + t * (left->m_y3 - left->m_y2);
left->m_x4 = m_x1 = left->m_x3 + t * (m_x2 - left->m_x3);
left->m_y4 = m_y1 = left->m_y3 + t * (m_y2 - left->m_y3);
}
PointF Bezier::pointAt(double t) const
{
// numerically more stable:
double x, y;
double m_t = 1. - t;
{
double a = m_x1 * m_t + m_x2 * t;
double b = m_x2 * m_t + m_x3 * t;
double c = m_x3 * m_t + m_x4 * t;
a = a * m_t + b * t;
b = b * m_t + c * t;
x = a * m_t + b * t;
}
{
double a = m_y1 * m_t + m_y2 * t;
double b = m_y2 * m_t + m_y3 * t;
double c = m_y3 * m_t + m_y4 * t;
a = a * m_t + b * t;
b = b * m_t + c * t;
y = a * m_t + b * t;
}
return PointF(x, y);
}
}

View file

@ -0,0 +1,55 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_BEZIER_H
#define MU_BEZIER_H
#include "geometry.h"
namespace mu {
class Bezier
{
public:
Bezier() = default;
Bezier(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4);
static Bezier fromPoints(const PointF& p1, const PointF& p2, const PointF& p3, const PointF& p4);
static void coefficients(double t, double& a, double& b, double& c, double& d);
Bezier bezierOnInterval(double t0, double t1) const;
PointF pt1() const { return PointF(m_x1, m_y1); }
PointF pt2() const { return PointF(m_x2, m_y2); }
PointF pt3() const { return PointF(m_x3, m_y3); }
PointF pt4() const { return PointF(m_x4, m_y4); }
PointF pointAt(double t) const;
private:
void parameterSplitLeft(double t, Bezier* left);
friend class PainterPath;
double m_x1, m_y1, m_x2, m_y2, m_x3, m_y3, m_x4, m_y4;
};
}
#endif // MU_BEZIER_H

View file

@ -23,7 +23,6 @@
#define MU_DRAW_BUFFEREDDRAWTYPES_H
#include <memory>
#include <QPainterPath>
#include "brush.h"
#include "drawtypes.h"
@ -31,6 +30,8 @@
#include "font.h"
#include "pen.h"
#include "pixmap.h"
#include "transform.h"
#include "painterpath.h"
namespace mu::draw {
enum class DrawMode {
@ -45,7 +46,7 @@ struct Scale {
};
struct DrawPath {
QPainterPath path;
PainterPath path;
Pen pen;
Brush brush;
DrawMode mode = DrawMode::StrokeAndFill;
@ -91,7 +92,7 @@ struct DrawData
Pen pen;
Brush brush;
Font font;
QTransform transform;
Transform transform;
bool isAntialiasing = false;
CompositionMode compositionMode = CompositionMode::SourceOver;
};

View file

@ -181,20 +181,20 @@ void BufferedPaintProvider::restore()
{
}
void BufferedPaintProvider::setTransform(const QTransform& transform)
void BufferedPaintProvider::setTransform(const Transform& transform)
{
DrawData::State& st = editableState();
st.transform = transform;
}
const QTransform& BufferedPaintProvider::transform() const
const Transform& BufferedPaintProvider::transform() const
{
return currentState().transform;
}
// drawing functions
void BufferedPaintProvider::drawPath(const QPainterPath& path)
void BufferedPaintProvider::drawPath(const PainterPath& path)
{
const DrawData::State& st = currentState();
DrawMode mode = DrawMode::StrokeAndFill;

View file

@ -65,11 +65,11 @@ public:
void save() override;
void restore() override;
void setTransform(const QTransform& transform) override;
const QTransform& transform() const override;
void setTransform(const Transform& transform) override;
const Transform& transform() const override;
// drawing functions
void drawPath(const QPainterPath& path) override;
void drawPath(const PainterPath& path) override;
void drawPolygon(const PointF* points, size_t pointCount, PolygonMode mode) override;
void drawText(const PointF& point, const QString& text) override;

View file

@ -22,7 +22,6 @@
#ifndef MU_DRAW_DRAWTYPES_H
#define MU_DRAW_DRAWTYPES_H
#include <QPainterPath>
#include <QVariant>
#include "geometry.h"

View file

@ -34,8 +34,6 @@
#include <QSizeF>
#endif
#include <QPainterPath>
namespace mu {
inline bool isEqual(qreal a1, qreal a2) { return qFuzzyCompare(a1, a2); }
inline bool isEqual(int a1, int a2) { return a1 == a2; }
@ -438,33 +436,6 @@ public:
using PolygonF = PolygonX<qreal>;
using Polygon = PolygonX<int>;
//! NOTE Temporary implementation
class PainterPath : public QPainterPath
{
public:
PainterPath() = default;
PainterPath(const QPainterPath& p)
: QPainterPath(p) {}
void moveTo(const PointF& p) { QPainterPath::moveTo(p.toQPointF()); }
inline void moveTo(qreal x, qreal y) { moveTo(PointF(x, y)); }
void cubicTo(const PointF& ctrlPt1, const PointF& ctrlPt2, const PointF& endPt)
{
QPainterPath::cubicTo(ctrlPt1.toQPointF(), ctrlPt2.toQPointF(), endPt.toQPointF());
}
inline void cubicTo(qreal ctrlPt1x, qreal ctrlPt1y, qreal ctrlPt2x, qreal ctrlPt2y, qreal endPtx, qreal endPty)
{
cubicTo(PointF(ctrlPt1x, ctrlPt1y), PointF(ctrlPt2x, ctrlPt2y), PointF(endPtx, endPty));
}
void translate(const PointF& offset) { QPainterPath::translate(offset.toQPointF()); }
inline void translate(qreal dx, qreal dy) { translate(PointF(dx, dy)); }
RectF boundingRect() const { return RectF::fromQRectF(QPainterPath::boundingRect()); }
};
// Impelemtation ==========================================
template<typename T>
RectX<T> RectX<T>::united(const RectX<T>& r) const
@ -723,7 +694,4 @@ Q_DECLARE_METATYPE(mu::SizeF)
Q_DECLARE_METATYPE(mu::RectF)
Q_DECLARE_METATYPE(mu::Rect)
Q_DECLARE_METATYPE(QPainterPath)
Q_DECLARE_METATYPE(mu::PainterPath)
#endif // MU_GEOMETRY_H

View file

@ -32,6 +32,7 @@
#include "font.h"
#include "pen.h"
#include "pixmap.h"
#include "transform.h"
namespace mu::draw {
class Painter;
@ -63,11 +64,11 @@ public:
virtual void save() = 0;
virtual void restore() = 0;
virtual void setTransform(const QTransform& transform) = 0;
virtual const QTransform& transform() const = 0;
virtual void setTransform(const Transform& transform) = 0;
virtual const Transform& transform() const = 0;
// drawing functions
virtual void drawPath(const QPainterPath& path) = 0;
virtual void drawPath(const PainterPath& path) = 0;
virtual void drawPolygon(const PointF* points, size_t pointCount, PolygonMode mode) = 0;
virtual void drawText(const PointF& point, const QString& text) = 0;

View file

@ -0,0 +1,84 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_MATRIX_H
#define MU_MATRIX_H
namespace mu {
class Matrix
{
public:
Matrix() {}
Matrix(double m11, double m12, double m21, double m22, double dx, double dy)
: m_11(m11)
, m_12(m12)
, m_21(m21)
, m_22(m22)
, m_dx(dx)
, m_dy(dy)
{
}
Matrix inverted(bool* invertible) const
{
double dtr = determinant();
if (dtr == 0.0) {
if (invertible) {
*invertible = false;
}
return Matrix();
} else {
if (invertible) {
*invertible = true;
}
double dinv = 1.0 / dtr;
return Matrix((m_22 * dinv), (-m_12 * dinv),
(-m_21 * dinv), (m_11 * dinv),
((m_21 * m_dy - m_22 * m_dx) * dinv),
((m_12 * m_dx - m_11 * m_dy) * dinv));
}
}
double determinant() const { return m_11 * m_22 - m_12 * m_21; }
double m11() const { return m_11; }
double m12() const { return m_12; }
double m21() const { return m_21; }
double m22() const { return m_22; }
double dx() const { return m_dx; }
double dy() const { return m_dy; }
private:
friend class Transform;
double m_11 = 1;
double m_12 = 0;
double m_21 = 0;
double m_22 = 1;
double m_dx = 0;
double m_dy = 0;
};
}
#endif // MU_MATRIX_H

View file

@ -21,9 +21,7 @@
*/
#include "painter.h"
#include "brush.h"
#include <QPainterPath>
#include "painterpath.h"
#include "log.h"
#ifndef NO_QT_SUPPORT
@ -204,7 +202,7 @@ void Painter::restore()
}
}
void Painter::setWorldTransform(const QTransform& matrix, bool combine)
void Painter::setWorldTransform(const Transform& matrix, bool combine)
{
State& st = editableState();
if (combine) {
@ -216,7 +214,7 @@ void Painter::setWorldTransform(const QTransform& matrix, bool combine)
updateMatrix();
}
const QTransform& Painter::worldTransform() const
const Transform& Painter::worldTransform() const
{
return state().worldTransform;
}
@ -273,7 +271,7 @@ void Painter::setViewport(const RectF& viewport)
// drawing functions
void Painter::fillPath(const QPainterPath& path, const Brush& brush)
void Painter::fillPath(const PainterPath& path, const Brush& brush)
{
Pen oldPen = this->pen();
Brush oldBrush = this->brush();
@ -286,7 +284,7 @@ void Painter::fillPath(const QPainterPath& path, const Brush& brush)
setBrush(oldBrush);
}
void Painter::strokePath(const QPainterPath& path, const Pen& pen)
void Painter::strokePath(const PainterPath& path, const Pen& pen)
{
Pen oldPen = this->pen();
Brush oldBrush = this->brush();
@ -299,7 +297,7 @@ void Painter::strokePath(const QPainterPath& path, const Pen& pen)
setBrush(oldBrush);
}
void Painter::drawPath(const QPainterPath& path)
void Painter::drawPath(const PainterPath& path)
{
m_provider->drawPath(path);
if (extended) {
@ -328,8 +326,8 @@ void Painter::drawLines(const PointF* pointPairs, size_t lineCount)
void Painter::drawRects(const RectF* rects, size_t rectCount)
{
for (size_t i = 0; i < rectCount; ++i) {
QPainterPath path;
path.addRect(rects[i].toQRectF());
PainterPath path;
path.addRect(rects[i]);
if (path.isEmpty()) {
continue;
}
@ -339,8 +337,8 @@ void Painter::drawRects(const RectF* rects, size_t rectCount)
void Painter::drawEllipse(const RectF& rect)
{
QPainterPath path;
path.addEllipse(rect.toQRectF());
PainterPath path;
path.addEllipse(rect);
m_provider->drawPath(path);
if (extended) {
extended->drawPath(path);
@ -376,23 +374,23 @@ void Painter::drawArc(const RectF& r, int a, int alen)
{
//! NOTE Copied from QPainter source code
QRectF rect = r.toQRectF().normalized();
RectF rect = r.normalized();
QPainterPath path;
PainterPath path;
path.arcMoveTo(rect, a / 16.0);
path.arcTo(rect, a / 16.0, alen / 16.0);
strokePath(path, pen());
}
void Painter::drawRoundedRect(const RectF& rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
void Painter::drawRoundedRect(const RectF& rect, qreal xRadius, qreal yRadius)
{
if (xRadius <= 0 || yRadius <= 0) { // draw normal rectangle
drawRect(rect);
return;
}
QPainterPath path;
path.addRoundedRect(rect.toQRectF(), xRadius, yRadius, mode);
PainterPath path;
path.addRoundedRect(rect, xRadius, yRadius);
drawPath(path);
}
@ -486,18 +484,18 @@ const Painter::State& Painter::state() const
return m_states.top();
}
QTransform Painter::makeViewTransform() const
Transform Painter::makeViewTransform() const
{
const State& st = state();
qreal scaleW = qreal(st.viewport.width()) / qreal(st.window.width());
qreal scaleH = qreal(st.viewport.height()) / qreal(st.window.height());
return QTransform(scaleW, 0, 0, scaleH, st.viewport.x() - st.window.x() * scaleW, st.viewport.y() - st.window.y() * scaleH);
return Transform(scaleW, 0, 0, scaleH, st.viewport.x() - st.window.x() * scaleW, st.viewport.y() - st.window.y() * scaleH);
}
void Painter::updateMatrix()
{
Painter::State& st = editableState();
st.transform = st.isWxF ? st.worldTransform : QTransform();
st.transform = st.isWxF ? st.worldTransform : Transform();
if (st.isVxF) {
st.transform *= st.viewTransform;
}

View file

@ -79,8 +79,8 @@ public:
void setBrush(const Brush& brush);
const Brush& brush() const;
void setWorldTransform(const QTransform& matrix, bool combine = false);
const QTransform& worldTransform() const;
void setWorldTransform(const Transform& matrix, bool combine = false);
const Transform& worldTransform() const;
void scale(qreal sx, qreal sy);
void rotate(qreal angle);
void translate(qreal dx, qreal dy);
@ -95,9 +95,9 @@ public:
void restore();
// drawing
void fillPath(const QPainterPath& path, const Brush& brush);
void drawPath(const QPainterPath& path);
void strokePath(const QPainterPath& path, const Pen& pen);
void fillPath(const PainterPath& path, const Brush& brush);
void drawPath(const PainterPath& path);
void strokePath(const PainterPath& path, const Pen& pen);
void drawLines(const LineF* lines, size_t lineCount);
void drawLines(const PointF* pointPairs, size_t lineCount);
@ -119,7 +119,7 @@ public:
void drawRects(const RectF* rects, size_t rectCount);
void drawRoundedRect(const RectF& rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode = Qt::AbsoluteSize);
void drawRoundedRect(const RectF& rect, qreal xRadius, qreal yRadius);
void drawEllipse(const RectF& rect);
inline void drawEllipse(const PointF& center, qreal rx, qreal ry);
@ -179,16 +179,16 @@ private:
RectF window;
RectF viewport;
bool isVxF = false;
QTransform viewTransform;
Transform viewTransform;
bool isWxF = false;
QTransform worldTransform; // World transformation matrix, not window and viewport
QTransform transform; // Complete transformation matrix
Transform worldTransform; // World transformation matrix, not window and viewport
Transform transform; // Complete transformation matrix
};
void init();
State& editableState();
const State& state() const;
QTransform makeViewTransform() const;
Transform makeViewTransform() const;
void updateMatrix();
bool endTarget(bool endDraw);

View file

@ -0,0 +1,653 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "painterpath.h"
#include "global/log.h"
namespace mu {
constexpr double pi = 3.14159265358979323846;
constexpr double pathKappa = 0.5522847498;
void findEllipseCoords(const RectF &r, double angle, double length,
PointF* startPoint, PointF *endPoint);
PointF curvesForArc(const RectF &rect, double startAngle, double sweepLength,
PointF *curves, int *point_count);
double angleForArc(double angle);
void PainterPath::moveTo(const PointF& p)
{
if (!hasValidCoords(p)) {
#ifdef DEBUG
LOGW() << "PainterPath::moveTo: Adding point with invalid coordinates, ignoring call");
#endif
return;
}
ensureData();
setDirty();
assert(!m_elements.empty());
m_requireMoveTo = false;
if (m_elements.back().type == ElementType::MoveToElement) {
m_elements.back().x = p.x();
m_elements.back().y = p.y();
} else {
m_elements.push_back({ p.x(), p.y(), ElementType::MoveToElement });
}
m_cStart = m_elements.size() - 1;
}
void PainterPath::lineTo(const PointF& p)
{
if (!hasValidCoords(p)) {
#ifdef DEBUG
LOGW() << "PainterPath::lineTo: Adding point with invalid coordinates, ignoring call";
#endif
return;
}
ensureData();
setDirty();
assert(!m_elements.empty());
maybeMoveTo();
if (p == PointF(m_elements.back()))
return;
m_elements.push_back({ p.x(), p.y(), ElementType::LineToElement });
m_convex = m_elements.size() == 3 || (m_elements.size() == 4 && isClosed());
}
void PainterPath::cubicTo(const PointF& ctrlPt1, const PointF& ctrlPt2, const PointF& endPt)
{
if (!hasValidCoords(ctrlPt1) || !hasValidCoords(ctrlPt2) || !hasValidCoords(endPt)) {
#ifdef DEBUG
LOGW() << "PainterPath::cubicTo: Adding point with invalid coordinates, ignoring call";
#endif
return;
}
ensureData();
setDirty();
assert(!m_elements.empty());
// Abort on empty curve as a stroker cannot handle this and the
// curve is irrelevant anyway.
if (ctrlPt1 == m_elements.back() && ctrlPt1 == ctrlPt2 && ctrlPt2 == endPt)
return;
maybeMoveTo();
m_elements.push_back({ ctrlPt1.x(), ctrlPt1.y(), ElementType::CurveToElement });
m_elements.push_back({ ctrlPt2.x(), ctrlPt2.y(), ElementType::CurveToDataElement });
m_elements.push_back({ endPt.x(), endPt.y(), ElementType::CurveToDataElement });
}
void PainterPath::translate(double dx, double dy)
{
if (dx == 0 && dy == 0)
return;
int m_elementsLeft = m_elements.size();
if (m_elementsLeft <= 0)
return;
setDirty();
PainterPath::Element *element = m_elements.data();
assert(element);
while (m_elementsLeft--) {
element->x += dx;
element->y += dy;
++element;
}
}
void PainterPath::translate(const PointF& offset)
{
translate(offset.x(), offset.y());
}
RectF PainterPath::boundingRect() const
{
if (m_dirtyBounds)
computeBoundingRect();
return m_bounds;
}
bool PainterPath::isEmpty() const
{
return m_elements.empty() || (m_elements.size() == 1 && m_elements.front().type == ElementType::MoveToElement);
}
int PainterPath::elementCount() const
{
return m_elements.size();
}
PainterPath::Element PainterPath::elementAt(int i) const
{
assert(i >= 0 && i < elementCount());
return m_elements.at(i);
}
void PainterPath::addRect(const RectF &r)
{
if (!hasValidCoords(r)) {
#ifdef DEBUG
LOGW() << "PainterPath::addRect: Adding point with invalid coordinates, ignoring call";
#endif
return;
}
if (r.isNull())
return;
ensureData();
setDirty();
bool first = m_elements.size() < 2;
moveTo(r.x(), r.y());
m_elements.push_back({ r.x() + r.width(), r.y(), ElementType::LineToElement });
m_elements.push_back({ r.x() + r.width(), r.y() + r.height(), ElementType::LineToElement });
m_elements.push_back({ r.x(), r.y() + r.height(), ElementType::LineToElement });
m_elements.push_back({ r.x(), r.y(), ElementType::LineToElement });
m_requireMoveTo = true;
m_convex = first;
}
void PainterPath::addEllipse(const RectF &boundingRect)
{
if (!hasValidCoords(boundingRect)) {
#ifdef DEBUG
LOGW() << "PainterPath::addEllipse: Adding point with invalid coordinates, ignoring call";
#endif
return;
}
if (boundingRect.isNull())
return;
ensureData();
setDirty();
bool first = m_elements.size() < 2;
PointF pts[12];
int point_count;
PointF start = curvesForArc(boundingRect, 0, -360, pts, &point_count);
moveTo(start);
cubicTo(pts[0], pts[1], pts[2]); // 0 -> 270
cubicTo(pts[3], pts[4], pts[5]); // 270 -> 180
cubicTo(pts[6], pts[7], pts[8]); // 180 -> 90
cubicTo(pts[9], pts[10], pts[11]); // 90 - >0
m_requireMoveTo = true;
m_convex = first;
}
void PainterPath::addRoundedRect(const RectF &rect, double xRadius, double yRadius)
{
RectF r = rect.normalized();
if (r.isNull())
return;
{
double w = r.width() / 2;
double h = r.height() / 2;
if (w == 0) {
xRadius = 0;
} else {
xRadius = 100 * qMin(xRadius, w) / w;
}
if (h == 0) {
yRadius = 0;
} else {
yRadius = 100 * qMin(yRadius, h) / h;
}
}
if (xRadius <= 0 || yRadius <= 0) { // add normal rectangle
addRect(r);
return;
}
double x = r.x();
double y = r.y();
double w = r.width();
double h = r.height();
double rxx2 = w*xRadius/100;
double ryy2 = h*yRadius/100;
ensureData();
setDirty();
bool first = m_elements.size() < 2;
arcMoveTo(x, y, rxx2, ryy2, 180);
arcTo(x, y, rxx2, ryy2, 180, -90);
arcTo(x+w-rxx2, y, rxx2, ryy2, 90, -90);
arcTo(x+w-rxx2, y+h-ryy2, rxx2, ryy2, 0, -90);
arcTo(x, y+h-ryy2, rxx2, ryy2, 270, -90);
closeSubpath();
m_requireMoveTo = true;
m_convex = first;
}
void PainterPath::arcMoveTo(const RectF &rect, double angle)
{
if (rect.isNull())
return;
PointF pt;
findEllipseCoords(rect, angle, 0, &pt, 0);
moveTo(pt);
}
void PainterPath::arcTo(const RectF &rect, double startAngle, double sweepLength)
{
if (!hasValidCoords(rect) || !isValidCoord(startAngle) || !isValidCoord(sweepLength)) {
#ifdef DEBUG
LOGW() << "PainterPath::arcTo: Adding point with invalid coordinates, ignoring call";
#endif
return;
}
if (rect.isNull())
return;
ensureData();
setDirty();
int point_count;
PointF pts[15];
PointF curve_start = curvesForArc(rect, startAngle, sweepLength, pts, &point_count);
lineTo(curve_start);
for (int i=0; i<point_count; i+=3) {
cubicTo(pts[i].x(), pts[i].y(),
pts[i+1].x(), pts[i+1].y(),
pts[i+2].x(), pts[i+2].y());
}
}
void PainterPath::closeSubpath()
{
if (isEmpty())
return;
setDirty();
m_requireMoveTo = true;
const Element &first = m_elements.at(m_cStart);
Element &last = m_elements.back();
if (first.x != last.x || first.y != last.y) {
if (qFuzzyCompare(first.x, last.x) && qFuzzyCompare(first.y, last.y)) {
last.x = first.x;
last.y = first.y;
} else {
m_elements.push_back({ first.x, first.y, ElementType::LineToElement } );
}
}
}
PainterPath::FillRule PainterPath::fillRule() const
{
return isEmpty() ? FillRule::OddEvenFill : m_fillRule;
}
void PainterPath::setFillRule(PainterPath::FillRule fillRule)
{
ensureData();
if (m_fillRule == fillRule)
return;
setDirty();
m_fillRule = fillRule;
}
void PainterPath::ensureData()
{
if (m_elements.empty())
{
m_elements.reserve(16);
m_elements.push_back({ 0, 0, ElementType::MoveToElement });
}
}
bool PainterPath::hasValidCoords(PointF p)
{
return isValidCoord(p.x()) && isValidCoord(p.y());
}
bool PainterPath::hasValidCoords(RectF r)
{
return isValidCoord(r.x()) && isValidCoord(r.y()) && isValidCoord(r.width()) && isValidCoord(r.height());
}
void PainterPath::computeBoundingRect() const
{
m_dirtyBounds = false;
double minx, maxx, miny, maxy;
minx = maxx = m_elements.at(0).x;
miny = maxy = m_elements.at(0).y;
for (size_t i=1; i<m_elements.size(); ++i) {
const Element &e = m_elements.at(i);
switch (e.type) {
case ElementType::MoveToElement:
case ElementType::LineToElement:
if (e.x > maxx) maxx = e.x;
else if (e.x < minx) minx = e.x;
if (e.y > maxy) maxy = e.y;
else if (e.y < miny) miny = e.y;
break;
case ElementType::CurveToElement:
{
Bezier b = Bezier::fromPoints(m_elements.at(i-1),
e,
m_elements.at(i+1),
m_elements.at(i+2));
RectF r = painterpathBezierExtrema(b);
double right = r.right();
double bottom = r.bottom();
if (r.x() < minx) minx = r.x();
if (right > maxx) maxx = right;
if (r.y() < miny) miny = r.y();
if (bottom > maxy) maxy = bottom;
i += 2;
}
break;
default:
break;
}
}
m_bounds = RectF(minx, miny, maxx - minx, maxy - miny);
}
void findEllipseCoords(const RectF &r, double angle, double length,
PointF* startPoint, PointF *endPoint)
{
if (r.isNull()) {
if (startPoint)
*startPoint = PointF();
if (endPoint)
*endPoint = PointF();
return;
}
double w2 = r.width() / 2;
double h2 = r.height() / 2;
double angles[2] = { angle, angle + length };
PointF *points[2] = { startPoint, endPoint };
for (int i = 0; i < 2; ++i) {
if (!points[i])
continue;
double theta = angles[i] - 360 * static_cast<int>(std::floor(angles[i] / 360));
double t = theta / 90;
// truncate
int quadrant = int(t);
t -= quadrant;
t = angleForArc(90 * t);
// swap x and y?
if (quadrant & 1)
t = 1 - t;
double a, b, c, d;
Bezier::coefficients(t, a, b, c, d);
PointF p(a + b + c*pathKappa, d + c + b*pathKappa);
// left quadrants
if (quadrant == 1 || quadrant == 2)
p.rx() = -p.x();
// top quadrants
if (quadrant == 0 || quadrant == 1)
p.ry() = -p.y();
*points[i] = r.center() + PointF(w2 * p.x(), h2 * p.y());
}
}
PointF curvesForArc(const RectF &rect, double startAngle, double sweepLength,
PointF *curves, int *point_count)
{
assert(point_count);
assert(curves);
*point_count = 0;
if (std::isnan(rect.x()) || std::isnan(rect.y()) || std::isnan(rect.width()) || std::isnan(rect.height())
|| std::isnan(startAngle) || std::isnan(sweepLength)) {
qWarning("PainterPath::arcTo: Adding arc where a parameter is NaN, results are undefined");
return PointF();
}
if (rect.isNull()) {
return PointF();
}
double x = rect.x();
double y = rect.y();
double w = rect.width();
double w2 = rect.width() / 2;
double w2k = w2 * pathKappa;
double h = rect.height();
double h2 = rect.height() / 2;
double h2k = h2 * pathKappa;
PointF points[16] =
{
// start point
PointF(x + w, y + h2),
// 0 -> 270 degrees
PointF(x + w, y + h2 + h2k),
PointF(x + w2 + w2k, y + h),
PointF(x + w2, y + h),
// 270 -> 180 degrees
PointF(x + w2 - w2k, y + h),
PointF(x, y + h2 + h2k),
PointF(x, y + h2),
// 180 -> 90 degrees
PointF(x, y + h2 - h2k),
PointF(x + w2 - w2k, y),
PointF(x + w2, y),
// 90 -> 0 degrees
PointF(x + w2 + w2k, y),
PointF(x + w, y + h2 - h2k),
PointF(x + w, y + h2)
};
if (sweepLength > 360) sweepLength = 360;
else if (sweepLength < -360) sweepLength = -360;
// Special case fast paths
if (startAngle == 0.0) {
if (sweepLength == 360.0) {
for (int i = 11; i >= 0; --i)
curves[(*point_count)++] = points[i];
return points[12];
} else if (sweepLength == -360.0) {
for (int i = 1; i <= 12; ++i)
curves[(*point_count)++] = points[i];
return points[0];
}
}
int startSegment = int(std::floor(startAngle / 90));
int endSegment = int(std::floor((startAngle + sweepLength) / 90));
double startT = (startAngle - startSegment * 90) / 90;
double endT = (startAngle + sweepLength - endSegment * 90) / 90;
int delta = sweepLength > 0 ? 1 : -1;
if (delta < 0) {
startT = 1 - startT;
endT = 1 - endT;
}
// avoid empty start segment
if (qFuzzyIsNull(startT - double(1))) {
startT = 0;
startSegment += delta;
}
// avoid empty end segment
if (qFuzzyIsNull(endT)) {
endT = 1;
endSegment -= delta;
}
startT = angleForArc(startT * 90);
endT = angleForArc(endT * 90);
const bool splitAtStart = !qFuzzyIsNull(startT);
const bool splitAtEnd = !qFuzzyIsNull(endT - double(1));
const int end = endSegment + delta;
// empty arc?
if (startSegment == end) {
const int quadrant = 3 - ((startSegment % 4) + 4) % 4;
const int j = 3 * quadrant;
return delta > 0 ? points[j + 3] : points[j];
}
PointF startPoint, endPoint;
findEllipseCoords(rect, startAngle, sweepLength, &startPoint, &endPoint);
for (int i = startSegment; i != end; i += delta) {
const int quadrant = 3 - ((i % 4) + 4) % 4;
const int j = 3 * quadrant;
Bezier b;
if (delta > 0)
b = Bezier::fromPoints(points[j + 3], points[j + 2], points[j + 1], points[j]);
else
b = Bezier::fromPoints(points[j], points[j + 1], points[j + 2], points[j + 3]);
// empty arc?
if (startSegment == endSegment && qFuzzyCompare(startT, endT))
return startPoint;
if (i == startSegment) {
if (i == endSegment && splitAtEnd)
b = b.bezierOnInterval(startT, endT);
else if (splitAtStart)
b = b.bezierOnInterval(startT, 1);
} else if (i == endSegment && splitAtEnd) {
b = b.bezierOnInterval(0, endT);
}
// push control points
curves[(*point_count)++] = b.pt2();
curves[(*point_count)++] = b.pt3();
curves[(*point_count)++] = b.pt4();
}
assert(*point_count > 0);
curves[*(point_count)-1] = endPoint;
return startPoint;
}
double angleForArc(double angle)
{
if (qFuzzyIsNull(angle))
return 0;
if (qFuzzyCompare(angle, double(90)))
return 1;
double radians = angle * (pi / 180);;
double cosAngle = std::cos(radians);
double sinAngle = std::sin(radians);
// initial guess
double tc = angle / 90;
// do some iterations of newton's method to approximate cosAngle
// finds the zero of the function b.pointAt(tc).x() - cosAngle
tc -= ((((2-3*pathKappa) * tc + 3*(pathKappa-1)) * tc) * tc + 1 - cosAngle) // value
/ (((6-9*pathKappa) * tc + 6*(pathKappa-1)) * tc); // derivative
tc -= ((((2-3*pathKappa) * tc + 3*(pathKappa-1)) * tc) * tc + 1 - cosAngle) // value
/ (((6-9*pathKappa) * tc + 6*(pathKappa-1)) * tc); // derivative
// initial guess
double ts = tc;
// do some iterations of newton's method to approximate sinAngle
// finds the zero of the function b.pointAt(tc).y() - sinAngle
ts -= ((((3*pathKappa-2) * ts - 6*pathKappa + 3) * ts + 3*pathKappa) * ts - sinAngle)
/ (((9*pathKappa-6) * ts + 12*pathKappa - 6) * ts + 3*pathKappa);
ts -= ((((3*pathKappa-2) * ts - 6*pathKappa + 3) * ts + 3*pathKappa) * ts - sinAngle)
/ (((9*pathKappa-6) * ts + 12*pathKappa - 6) * ts + 3*pathKappa);
// use the average of the t that best approximates cosAngle
// and the t that best approximates sinAngle
double t = 0.5 * (tc + ts);
return t;
}
#define BEZIER_A(bezier, coord) 3 * (-bezier.m_##coord##1 \
+ 3*bezier.m_##coord##2 \
- 3*bezier.m_##coord##3 \
+bezier.m_##coord##4)
#define BEZIER_B(bezier, coord) 6 * (bezier.m_##coord##1 \
- 2*bezier.m_##coord##2 \
+ bezier.m_##coord##3)
#define BEZIER_C(bezier, coord) 3 * (- bezier.m_##coord##1 \
+ bezier.m_##coord##2)
#define BEZIER_CHECK_T(bezier, t) \
if (t >= 0 && t <= 1) { \
PointF p(b.pointAt(t)); \
if (p.x() < minx) minx = p.x(); \
else if (p.x() > maxx) maxx = p.x(); \
if (p.y() < miny) miny = p.y(); \
else if (p.y() > maxy) maxy = p.y(); \
}
RectF PainterPath::painterpathBezierExtrema(const Bezier &b)
{
double minx, miny, maxx, maxy;
// initialize with end points
if (b.m_x1 < b.m_x4) {
minx = b.m_x1;
maxx = b.m_x4;
} else {
minx = b.m_x4;
maxx = b.m_x1;
}
if (b.m_y1 < b.m_y4) {
miny = b.m_y1;
maxy = b.m_y4;
} else {
miny = b.m_y4;
maxy = b.m_y1;
}
// Update for the X extrema
{
double ax = BEZIER_A(b, x);
double bx = BEZIER_B(b, x);
double cx = BEZIER_C(b, x);
// specialcase quadratic curves to avoid div by zero
if (qFuzzyIsNull(ax)) {
// linear curves are covered by initialization.
if (!qFuzzyIsNull(bx)) {
double t = -cx / bx;
BEZIER_CHECK_T(b, t);
}
} else {
const double tx = bx * bx - 4 * ax * cx;
if (tx >= 0) {
double temp = std::sqrt(tx);
double rcp = 1 / (2 * ax);
double t1 = (-bx + temp) * rcp;
BEZIER_CHECK_T(b, t1);
double t2 = (-bx - temp) * rcp;
BEZIER_CHECK_T(b, t2);
}
}
}
// Update for the Y extrema
{
double ay = BEZIER_A(b, y);
double by = BEZIER_B(b, y);
double cy = BEZIER_C(b, y);
// specialcase quadratic curves to avoid div by zero
if (qFuzzyIsNull(ay)) {
// linear curves are covered by initialization.
if (!qFuzzyIsNull(by)) {
double t = -cy / by;
BEZIER_CHECK_T(b, t);
}
} else {
const double ty = by * by - 4 * ay * cy;
if (ty > 0) {
double temp = std::sqrt(ty);
double rcp = 1 / (2 * ay);
double t1 = (-by + temp) * rcp;
BEZIER_CHECK_T(b, t1);
double t2 = (-by - temp) * rcp;
BEZIER_CHECK_T(b, t2);
}
}
}
return RectF(minx, miny, maxx - minx, maxy - miny);
}
void PainterPath::setDirty()
{
m_dirtyBounds = true;
m_convex = false;
}
}

View file

@ -0,0 +1,169 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MU_PAINTERPATH_H
#define MU_PAINTERPATH_H
#include "geometry.h"
#include "bezier.h"
#include "drawtypes.h"
namespace mu {
class PainterPath
{
public:
enum class ElementType {
MoveToElement,
LineToElement,
CurveToElement,
CurveToDataElement
};
enum class FillRule {
OddEvenFill,
WindingFill
};
class Element
{
public:
double x;
double y;
ElementType type;
bool isMoveTo() const { return type == ElementType::MoveToElement; }
bool isLineTo() const { return type == ElementType::LineToElement; }
bool isCurveTo() const { return type == ElementType::CurveToElement; }
operator PointF() const {
return PointF(x, y);
}
bool operator==(const Element& e) const
{
return qFuzzyCompare(x, e.x)
&& qFuzzyCompare(y, e.y) && type == e.type;
}
inline bool operator!=(const Element& e) const { return !operator==(e); }
};
PainterPath() = default;
void moveTo(const PointF& p);
inline void moveTo(double x, double y) { moveTo(PointF(x, y)); }
void lineTo(const PointF& p);
inline void lineTo(double x, double y) { lineTo(PointF(x, y)); }
void cubicTo(const PointF& ctrlPt1, const PointF& ctrlPt2, const PointF& endPt);
inline void cubicTo(double ctrlPt1x, double ctrlPt1y, double ctrlPt2x, double ctrlPt2y, double endPtx, double endPty)
{
cubicTo(PointF(ctrlPt1x, ctrlPt1y), PointF(ctrlPt2x, ctrlPt2y), PointF(endPtx, endPty));
}
void translate(const PointF& offset);
void translate(double dx, double dy);
RectF boundingRect() const;
bool isEmpty() const;
int elementCount() const;
PainterPath::Element elementAt(int i) const;
void addRect(const RectF& r);
inline void addRect(double x, double y, double w, double h)
{
addRect(RectF(x, y, w, h));
}
void addEllipse(const RectF& boundingRect);
void addRoundedRect(const RectF& rect, double xRadius, double yRadius);
void arcMoveTo(const RectF& rect, double angle);
inline void arcMoveTo(double x, double y, double w, double h, double angle)
{
arcMoveTo(RectF(x, y, w, h), angle);
}
void arcTo(const RectF& rect, double startAngle, double sweepLength);
inline void arcTo(double x, double y, double w, double h, double startAngle, double arcLength)
{
arcTo(RectF(x, y, w, h), startAngle, arcLength);
}
void closeSubpath();
PainterPath::FillRule fillRule() const;
void setFillRule(PainterPath::FillRule fillRule);
private:
void ensureData();
void computeBoundingRect() const;
static bool hasValidCoords(PointF p);
inline void maybeMoveTo()
{
if (m_requireMoveTo) {
Element e = m_elements.back();
e.type = ElementType::MoveToElement;
m_elements.push_back(e);
m_requireMoveTo = false;
}
}
inline bool isClosed() const
{
const PainterPath::Element& first = m_elements.at(m_cStart);
const PainterPath::Element& last = m_elements.back();
return first.x == last.x && first.y == last.y;
}
static inline bool isValidCoord(double c)
{
return std::isfinite(c) && std::fabs(c) < 1e128;
}
static bool hasValidCoords(RectF r);
static RectF painterpathBezierExtrema(const Bezier& b);
void setDirty();
int m_cStart = 0;
mutable RectF m_bounds;
bool m_requireMoveTo = false;
mutable bool m_dirtyBounds = false;
bool m_convex = false;
FillRule m_fillRule;
std::vector<Element> m_elements;
friend class Transform;
};
}
Q_DECLARE_METATYPE(mu::PainterPath)
#endif // MU_PAINTERPATH_H

View file

@ -28,10 +28,13 @@
#include <QGlyphRun>
#include <QPixmapCache>
#include <QStaticText>
#include <QPainterPath>
#include "fontcompat.h"
#include "utils/drawlogger.h"
#include "utils/drawjson.h"
#include "log.h"
#include "transform.h"
using namespace mu::draw;
@ -170,22 +173,24 @@ void QPainterProvider::restore()
m_brush = Brush::fromQBrush(m_painter->brush());
}
void QPainterProvider::setTransform(const QTransform& transform)
void QPainterProvider::setTransform(const Transform& transform)
{
m_transform = transform;
m_painter->setTransform(transform);
m_painter->setTransform(Transform::toQTransform(m_transform));
}
const QTransform& QPainterProvider::transform() const
const mu::Transform& QPainterProvider::transform() const
{
return m_painter->transform();
return m_transform;
}
// drawing functions
void QPainterProvider::drawPath(const QPainterPath& path)
void QPainterProvider::drawPath(const PainterPath& path)
{
m_painter->drawPath(path);
QPainterPath qpath;
DrawBufferJson::qPainterPathfromObj(DrawBufferJson::painterPathToObj(path), qpath);
m_painter->drawPath(qpath);
}
void QPainterProvider::drawPolygon(const PointF* points, size_t pointCount, PolygonMode mode)

View file

@ -64,11 +64,11 @@ public:
void save() override;
void restore() override;
void setTransform(const QTransform& transform) override;
const QTransform& transform() const override;
void setTransform(const Transform& transform) override;
const Transform& transform() const override;
// drawing functions
void drawPath(const QPainterPath& path) override;
void drawPath(const PainterPath& path) override;
void drawPolygon(const PointF* points, size_t pointCount, PolygonMode mode) override;
void drawText(const PointF& point, const QString& text) override;
@ -94,7 +94,7 @@ private:
Pen m_pen;
Brush m_brush;
QTransform m_transform;
Transform m_transform;
};
}

View file

@ -0,0 +1,724 @@
/*
* SPDX-License-Identifier: GPL-3.0-only
* MuseScore-CLA-applies
*
* MuseScore
* Music Composition & Notation
*
* Copyright (C) 2021 MuseScore BVBA and others
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "transform.h"
#include "painterpath.h"
namespace mu {
Transform::Transform(double h11, double h12, double h21, double h22, double dx, double dy)
: m_affine(h11, h12, h21, h22, dx, dy), m_dirty(TransformationType::Shear)
{
}
Transform::Transform(double h11, double h12, double h13,
double h21, double h22, double h23,
double h31, double h32, double h33)
: m_affine(h11, h12, h21, h22, h31, h32)
, m_13(h13), m_23(h23), m_33(h33)
, m_type(TransformationType::None)
, m_dirty(TransformationType::Project)
{
}
Transform Transform::operator*(const Transform& m) const
{
const TransformationType otherType = m.inline_type();
if (otherType == TransformationType::None) {
return *this;
}
const TransformationType thisType = inline_type();
if (thisType == TransformationType::None) {
return m;
}
Transform t;
TransformationType type = std::max(thisType, otherType);
switch (type) {
case TransformationType::None:
break;
case TransformationType::Translate:
t.m_affine.m_dx = m_affine.m_dx + m.m_affine.m_dx;
t.m_affine.m_dy += m_affine.m_dy + m.m_affine.m_dy;
break;
case TransformationType::Scale:
{
double m11 = m_affine.m_11 * m.m_affine.m_11;
double m22 = m_affine.m_22 * m.m_affine.m_22;
double m31 = m_affine.m_dx * m.m_affine.m_11 + m.m_affine.m_dx;
double m32 = m_affine.m_dy * m.m_affine.m_22 + m.m_affine.m_dy;
t.m_affine.m_11 = m11;
t.m_affine.m_22 = m22;
t.m_affine.m_dx = m31;
t.m_affine.m_dy = m32;
break;
}
case TransformationType::Rotate:
case TransformationType::Shear:
{
double m11 = m_affine.m_11 * m.m_affine.m_11 + m_affine.m_12 * m.m_affine.m_21;
double m12 = m_affine.m_11 * m.m_affine.m_12 + m_affine.m_12 * m.m_affine.m_22;
double m21 = m_affine.m_21 * m.m_affine.m_11 + m_affine.m_22 * m.m_affine.m_21;
double m22 = m_affine.m_21 * m.m_affine.m_12 + m_affine.m_22 * m.m_affine.m_22;
double m31 = m_affine.m_dx * m.m_affine.m_11 + m_affine.m_dy * m.m_affine.m_21 + m.m_affine.m_dx;
double m32 = m_affine.m_dx * m.m_affine.m_12 + m_affine.m_dy * m.m_affine.m_22 + m.m_affine.m_dy;
t.m_affine.m_11 = m11;
t.m_affine.m_12 = m12;
t.m_affine.m_21 = m21;
t.m_affine.m_22 = m22;
t.m_affine.m_dx = m31;
t.m_affine.m_dy = m32;
break;
}
case TransformationType::Project:
{
double m11 = m_affine.m_11 * m.m_affine.m_11 + m_affine.m_12 * m.m_affine.m_21 + m_13 * m.m_affine.m_dx;
double m12 = m_affine.m_11 * m.m_affine.m_12 + m_affine.m_12 * m.m_affine.m_22 + m_13 * m.m_affine.m_dy;
double m13 = m_affine.m_11 * m.m_13 + m_affine.m_12 * m.m_23 + m_13 * m.m_33;
double m21 = m_affine.m_21 * m.m_affine.m_11 + m_affine.m_22 * m.m_affine.m_21 + m_23 * m.m_affine.m_dx;
double m22 = m_affine.m_21 * m.m_affine.m_12 + m_affine.m_22 * m.m_affine.m_22 + m_23 * m.m_affine.m_dy;
double m23 = m_affine.m_21 * m.m_13 + m_affine.m_22 * m.m_23 + m_23 * m.m_33;
double m31 = m_affine.m_dx * m.m_affine.m_11 + m_affine.m_dy * m.m_affine.m_21 + m_33 * m.m_affine.m_dx;
double m32 = m_affine.m_dx * m.m_affine.m_12 + m_affine.m_dy * m.m_affine.m_22 + m_33 * m.m_affine.m_dy;
double m33 = m_affine.m_dx * m.m_13 + m_affine.m_dy * m.m_23 + m_33 * m.m_33;
t.m_affine.m_11 = m11;
t.m_affine.m_12 = m12;
t.m_13 = m13;
t.m_affine.m_21 = m21;
t.m_affine.m_22 = m22;
t.m_23 = m23;
t.m_affine.m_dx = m31;
t.m_affine.m_dy = m32;
t.m_33 = m33;
}
}
t.m_dirty = type;
t.m_type = type;
return t;
}
Transform& Transform::operator*=(const Transform& o)
{
const TransformationType otherType = o.inline_type();
if (otherType == TransformationType::None) {
return *this;
}
const TransformationType thisType = inline_type();
if (thisType == TransformationType::None) {
return operator=(o);
}
TransformationType t = std::max(thisType, otherType);
switch (t) {
case TransformationType::None:
break;
case TransformationType::Translate:
m_affine.m_dx += o.m_affine.m_dx;
m_affine.m_dy += o.m_affine.m_dy;
break;
case TransformationType::Scale:
{
double m11 = m_affine.m_11 * o.m_affine.m_11;
double m22 = m_affine.m_22 * o.m_affine.m_22;
double m31 = m_affine.m_dx * o.m_affine.m_11 + o.m_affine.m_dx;
double m32 = m_affine.m_dy * o.m_affine.m_22 + o.m_affine.m_dy;
m_affine.m_11 = m11;
m_affine.m_22 = m22;
m_affine.m_dx = m31;
m_affine.m_dy = m32;
break;
}
case TransformationType::Rotate:
case TransformationType::Shear:
{
double m11 = m_affine.m_11 * o.m_affine.m_11 + m_affine.m_12 * o.m_affine.m_21;
double m12 = m_affine.m_11 * o.m_affine.m_12 + m_affine.m_12 * o.m_affine.m_22;
double m21 = m_affine.m_21 * o.m_affine.m_11 + m_affine.m_22 * o.m_affine.m_21;
double m22 = m_affine.m_21 * o.m_affine.m_12 + m_affine.m_22 * o.m_affine.m_22;
double m31 = m_affine.m_dx * o.m_affine.m_11 + m_affine.m_dy * o.m_affine.m_21 + o.m_affine.m_dx;
double m32 = m_affine.m_dx * o.m_affine.m_12 + m_affine.m_dy * o.m_affine.m_22 + o.m_affine.m_dy;
m_affine.m_11 = m11;
m_affine.m_12 = m12;
m_affine.m_21 = m21;
m_affine.m_22 = m22;
m_affine.m_dx = m31;
m_affine.m_dy = m32;
break;
}
case TransformationType::Project:
{
double m11 = m_affine.m_11 * o.m_affine.m_11 + m_affine.m_12 * o.m_affine.m_21 + m_13 * o.m_affine.m_dx;
double m12 = m_affine.m_11 * o.m_affine.m_12 + m_affine.m_12 * o.m_affine.m_22 + m_13 * o.m_affine.m_dy;
double m13 = m_affine.m_11 * o.m_13 + m_affine.m_12 * o.m_23 + m_13 * o.m_33;
double m21 = m_affine.m_21 * o.m_affine.m_11 + m_affine.m_22 * o.m_affine.m_21 + m_23 * o.m_affine.m_dx;
double m22 = m_affine.m_21 * o.m_affine.m_12 + m_affine.m_22 * o.m_affine.m_22 + m_23 * o.m_affine.m_dy;
double m23 = m_affine.m_21 * o.m_13 + m_affine.m_22 * o.m_23 + m_23 * o.m_33;
double m31 = m_affine.m_dx * o.m_affine.m_11 + m_affine.m_dy * o.m_affine.m_21 + m_33 * o.m_affine.m_dx;
double m32 = m_affine.m_dx * o.m_affine.m_12 + m_affine.m_dy * o.m_affine.m_22 + m_33 * o.m_affine.m_dy;
double m33 = m_affine.m_dx * o.m_13 + m_affine.m_dy * o.m_23 + m_33 * o.m_33;
m_affine.m_11 = m11;
m_affine.m_12 = m12;
m_13 = m13;
m_affine.m_21 = m21;
m_affine.m_22 = m22;
m_23 = m23;
m_affine.m_dx = m31;
m_affine.m_dy = m32;
m_33 = m33;
}
}
m_dirty = t;
m_type = t;
return *this;
}
void Transform::setMatrix(double m11, double m12, double m13,
double m21, double m22, double m23,
double m31, double m32, double m33)
{
m_affine.m_11 = m11;
m_affine.m_12 = m12;
m_13 = m13;
m_affine.m_21 = m21;
m_affine.m_22 = m22;
m_23 = m23;
m_affine.m_dx = m31;
m_affine.m_dy = m32;
m_33 = m33;
}
void Transform::reset()
{
m_affine.m_11 = m_affine.m_22 = m_33 = 1.0;
m_affine.m_12 = m_13 = m_affine.m_21 = m_23 = m_affine.m_dx = m_affine.m_dy = 0;
}
Transform Transform::adjoint() const
{
double h11, h12, h13,
h21, h22, h23,
h31, h32, h33;
h11 = m_affine.m_22 * m_33 - m_23 * m_affine.m_dy;
h21 = m_23 * m_affine.m_dx - m_affine.m_21 * m_33;
h31 = m_affine.m_21 * m_affine.m_dy - m_affine.m_22 * m_affine.m_dx;
h12 = m_13 * m_affine.m_dy - m_affine.m_12 * m_33;
h22 = m_affine.m_11 * m_33 - m_13 * m_affine.m_dx;
h32 = m_affine.m_12 * m_affine.m_dx - m_affine.m_11 * m_affine.m_dy;
h13 = m_affine.m_12 * m_23 - m_13 * m_affine.m_22;
h23 = m_13 * m_affine.m_21 - m_affine.m_11 * m_23;
h33 = m_affine.m_11 * m_affine.m_22 - m_affine.m_12 * m_affine.m_21;
return Transform(h11, h12, h13,
h21, h22, h23,
h31, h32, h33);
}
Transform::TransformationType Transform::type() const
{
if (m_dirty == TransformationType::None || m_dirty < m_type) {
return m_type;
}
switch (m_dirty) {
case TransformationType::Project:
if (!qFuzzyIsNull(m_13) || !qFuzzyIsNull(m_23) || !qFuzzyIsNull(m_33 - 1)) {
m_type = TransformationType::Project;
break;
}
// fall through
case TransformationType::Shear:
case TransformationType::Rotate:
if (!qFuzzyIsNull(m_affine.m_12) || !qFuzzyIsNull(m_affine.m_21)) {
const double dot = m_affine.m_11 * m_affine.m_12 + m_affine.m_21 * m_affine.m_22;
if (qFuzzyIsNull(dot)) {
m_type = TransformationType::Rotate;
} else {
m_type = TransformationType::Shear;
}
break;
}
// fall through
case TransformationType::Scale:
if (!qFuzzyIsNull(m_affine.m_11 - 1) || !qFuzzyIsNull(m_affine.m_22 - 1)) {
m_type = TransformationType::Scale;
break;
}
// fall through
case TransformationType::Translate:
if (!qFuzzyIsNull(m_affine.m_dx) || !qFuzzyIsNull(m_affine.m_dy)) {
m_type = TransformationType::Translate;
break;
}
// fall through
case TransformationType::None:
m_type = TransformationType::None;
break;
}
m_dirty = TransformationType::None;
return m_type;
}
PointF Transform::map(const PointF& p) const
{
double fx = p.x();
double fy = p.y();
double x = 0, y = 0;
TransformationType t = inline_type();
switch (t) {
case TransformationType::None:
x = fx;
y = fy;
break;
case TransformationType::Translate:
x = fx + m_affine.m_dx;
y = fy + m_affine.m_dy;
break;
case TransformationType::Scale:
x = m_affine.m_11 * fx + m_affine.m_dx;
y = m_affine.m_22 * fy + m_affine.m_dy;
break;
case TransformationType::Rotate:
case TransformationType::Shear:
case TransformationType::Project:
x = m_affine.m_11 * fx + m_affine.m_21 * fy + m_affine.m_dx;
y = m_affine.m_12 * fx + m_affine.m_22 * fy + m_affine.m_dy;
if (t == TransformationType::Project) {
double w = 1. / (m_13 * fx + m_23 * fy + m_33);
x *= w;
y *= w;
}
}
return PointF(x, y);
}
LineF Transform::map(const LineF& l) const
{
double fx1 = l.x1();
double fy1 = l.y1();
double fx2 = l.x2();
double fy2 = l.y2();
double x1 = 0, y1 = 0, x2 = 0, y2 = 0;
TransformationType t = inline_type();
switch (t) {
case TransformationType::None:
x1 = fx1;
y1 = fy1;
x2 = fx2;
y2 = fy2;
break;
case TransformationType::Translate:
x1 = fx1 + m_affine.m_dx;
y1 = fy1 + m_affine.m_dy;
x2 = fx2 + m_affine.m_dx;
y2 = fy2 + m_affine.m_dy;
break;
case TransformationType::Scale:
x1 = m_affine.m_11 * fx1 + m_affine.m_dx;
y1 = m_affine.m_22 * fy1 + m_affine.m_dy;
x2 = m_affine.m_11 * fx2 + m_affine.m_dx;
y2 = m_affine.m_22 * fy2 + m_affine.m_dy;
break;
case TransformationType::Rotate:
case TransformationType::Shear:
case TransformationType::Project:
x1 = m_affine.m_11 * fx1 + m_affine.m_21 * fy1 + m_affine.m_dx;
y1 = m_affine.m_12 * fx1 + m_affine.m_22 * fy1 + m_affine.m_dy;
x2 = m_affine.m_11 * fx2 + m_affine.m_21 * fy2 + m_affine.m_dx;
y2 = m_affine.m_12 * fx2 + m_affine.m_22 * fy2 + m_affine.m_dy;
if (t == TransformationType::Project) {
double w = 1. / (m_13 * fx1 + m_23 * fy1 + m_33);
x1 *= w;
y1 *= w;
w = 1. / (m_13 * fx2 + m_23 * fy2 + m_33);
x2 *= w;
y2 *= w;
}
}
return LineF(x1, y1, x2, y2);
}
PainterPath Transform::map(const PainterPath& path) const
{
TransformationType t = inline_type();
if (t == TransformationType::None || path.elementCount() == 0) {
return path;
}
// TransformationType::Project is not supported here, it was not used while mapping PainterPath and it needs a lot of code to handle
PainterPath copy = path;
if (t == TransformationType::Translate) {
copy.translate(m_affine.m_dx, m_affine.m_dy);
} else {
// Full xform
for (int i = 0; i < path.elementCount(); ++i) {
PainterPath::Element& e = copy.m_elements[i];
mapElement(e.x, e.y, t);
}
}
return copy;
}
Transform& Transform::rotate(double a)
{
const double deg2rad = double(0.017453292519943295769); // pi/180
if (a == 0) {
return *this;
}
#ifdef DEBUG
if (std::isnan(a)) {
nanWarning("rotate");
return *this;
}
#endif
double sina = 0;
double cosa = 0;
if (a == 90. || a == -270.) {
sina = 1.;
} else if (a == 270. || a == -90.) {
sina = -1.;
} else if (a == 180.) {
cosa = -1.;
} else {
double b = deg2rad * a;
sina = std::sin(b);
cosa = std::cos(b);
}
switch (inline_type()) {
case TransformationType::None:
case TransformationType::Translate:
m_affine.m_11 = cosa;
m_affine.m_12 = sina;
m_affine.m_21 = -sina;
m_affine.m_22 = cosa;
break;
case TransformationType::Scale: {
double tm11 = cosa * m_affine.m_11;
double tm12 = sina * m_affine.m_22;
double tm21 = -sina * m_affine.m_11;
double tm22 = cosa * m_affine.m_22;
m_affine.m_11 = tm11;
m_affine.m_12 = tm12;
m_affine.m_21 = tm21;
m_affine.m_22 = tm22;
break;
}
case TransformationType::Project: {
double tm13 = cosa * m_13 + sina * m_23;
double tm23 = -sina * m_13 + cosa * m_23;
m_13 = tm13;
m_23 = tm23;
// fallthrough
}
case TransformationType::Rotate:
case TransformationType::Shear: {
double tm11 = cosa * m_affine.m_11 + sina * m_affine.m_21;
double tm12 = cosa * m_affine.m_12 + sina * m_affine.m_22;
double tm21 = -sina * m_affine.m_11 + cosa * m_affine.m_21;
double tm22 = -sina * m_affine.m_12 + cosa * m_affine.m_22;
m_affine.m_11 = tm11;
m_affine.m_12 = tm12;
m_affine.m_21 = tm21;
m_affine.m_22 = tm22;
break;
}
}
if (m_dirty < TransformationType::Rotate) {
m_dirty = TransformationType::Rotate;
}
return *this;
}
Transform& Transform::rotateRadians(double a)
{
#ifdef DEBUG
if (std::isnan(a)) {
nanWarning("rotateRadians");
return *this;
}
#endif
double sina = std::sin(a);
double cosa = std::cos(a);
switch (inline_type()) {
case TransformationType::None:
case TransformationType::Translate:
m_affine.m_11 = cosa;
m_affine.m_12 = sina;
m_affine.m_21 = -sina;
m_affine.m_22 = cosa;
break;
case TransformationType::Scale: {
double tm11 = cosa * m_affine.m_11;
double tm12 = sina * m_affine.m_22;
double tm21 = -sina * m_affine.m_11;
double tm22 = cosa * m_affine.m_22;
m_affine.m_11 = tm11;
m_affine.m_12 = tm12;
m_affine.m_21 = tm21;
m_affine.m_22 = tm22;
break;
}
case TransformationType::Project: {
double tm13 = cosa * m_13 + sina * m_23;
double tm23 = -sina * m_13 + cosa * m_23;
m_13 = tm13;
m_23 = tm23;
// fallthrough
}
case TransformationType::Rotate:
case TransformationType::Shear: {
double tm11 = cosa * m_affine.m_11 + sina * m_affine.m_21;
double tm12 = cosa * m_affine.m_12 + sina * m_affine.m_22;
double tm21 = -sina * m_affine.m_11 + cosa * m_affine.m_21;
double tm22 = -sina * m_affine.m_12 + cosa * m_affine.m_22;
m_affine.m_11 = tm11;
m_affine.m_12 = tm12;
m_affine.m_21 = tm21;
m_affine.m_22 = tm22;
break;
}
}
if (m_dirty < TransformationType::Rotate) {
m_dirty = TransformationType::Rotate;
}
return *this;
}
Transform& Transform::translate(double dx, double dy)
{
if (dx == 0 && dy == 0) {
return *this;
}
#ifdef DEBUG
if (std::isnan(dx) | td::isnan(dy)) {
nanWarning("translate");
return *this;
}
#endif
switch (inline_type()) {
case TransformationType::None:
m_affine.m_dx = dx;
m_affine.m_dy = dy;
break;
case TransformationType::Translate:
m_affine.m_dx += dx;
m_affine.m_dy += dy;
break;
case TransformationType::Scale:
m_affine.m_dx += dx * m_affine.m_11;
m_affine.m_dy += dy * m_affine.m_22;
break;
case TransformationType::Project:
m_33 += dx * m_13 + dy * m_23;
// fallthrough
case TransformationType::Shear:
case TransformationType::Rotate:
m_affine.m_dx += dx * m_affine.m_11 + dy * m_affine.m_21;
m_affine.m_dy += dy * m_affine.m_22 + dx * m_affine.m_12;
break;
}
if (m_dirty < TransformationType::Translate) {
m_dirty = TransformationType::Translate;
}
return *this;
}
Transform& Transform::scale(double sx, double sy)
{
if (sx == 1 && sy == 1) {
return *this;
}
#ifdef DEBUG
if (std::isnan(sx) | td::isnan(sy)) {
nanWarning("scale");
return *this;
}
#endif
switch (inline_type()) {
case TransformationType::None:
case TransformationType::Translate:
m_affine.m_11 = sx;
m_affine.m_22 = sy;
break;
case TransformationType::Project:
m_13 *= sx;
m_23 *= sy;
case TransformationType::Rotate:
case TransformationType::Shear:
m_affine.m_12 *= sx;
m_affine.m_21 *= sy;
// fall through
case TransformationType::Scale:
m_affine.m_11 *= sx;
m_affine.m_22 *= sy;
break;
}
if (m_dirty < TransformationType::Scale) {
m_dirty = TransformationType::Scale;
}
return *this;
}
Transform& Transform::shear(double sh, double sv)
{
if (sh == 0 && sv == 0) {
return *this;
}
#ifdef DEBUG
if (std::isnan(sh) | std::isnan(sv)) {
nanWarning("shear");
return *this;
}
#endif
switch (inline_type()) {
case TransformationType::None:
case TransformationType::Translate:
m_affine.m_12 = sv;
m_affine.m_21 = sh;
break;
case TransformationType::Scale:
m_affine.m_12 = sv * m_affine.m_22;
m_affine.m_21 = sh * m_affine.m_11;
break;
case TransformationType::Project: {
double tm13 = sv * m_23;
double tm23 = sh * m_13;
m_13 += tm13;
m_23 += tm23;
}
// fallthrough
case TransformationType::Rotate:
case TransformationType::Shear: {
double tm11 = sv * m_affine.m_21;
double tm22 = sh * m_affine.m_12;
double tm12 = sv * m_affine.m_22;
double tm21 = sh * m_affine.m_11;
m_affine.m_11 += tm11;
m_affine.m_12 += tm12;
m_affine.m_21 += tm21;
m_affine.m_22 += tm22;
break;
}
}
if (m_dirty < TransformationType::Shear) {
m_dirty = TransformationType::Shear;
}
return *this;
}
Transform Transform::inverted() const
{
Transform invert;
bool inv = true;
switch (inline_type()) {
case TransformationType::None:
break;
case TransformationType::Translate:
invert.m_affine.m_dx = -m_affine.m_dx;
invert.m_affine.m_dy = -m_affine.m_dy;
break;
case TransformationType::Scale:
inv = !qFuzzyIsNull(m_affine.m_11);
inv &= !qFuzzyIsNull(m_affine.m_22);
if (inv) {
invert.m_affine.m_11 = 1. / m_affine.m_11;
invert.m_affine.m_22 = 1. / m_affine.m_22;
invert.m_affine.m_dx = -m_affine.m_dx * invert.m_affine.m_11;
invert.m_affine.m_dy = -m_affine.m_dy * invert.m_affine.m_22;
}
break;
case TransformationType::Rotate:
case TransformationType::Shear:
invert.m_affine = m_affine.inverted(&inv);
break;
default:
// general case
double det = determinant();
inv = !qFuzzyIsNull(det);
if (inv) {
invert = adjoint() / det;
}
break;
}
if (inv) {
// inverting doesn't change the type
invert.m_type = m_type;
invert.m_dirty = m_dirty;
}
return invert;
}
void Transform::mapElement(double& nx, double& ny, TransformationType t) const
{
constexpr double nearClip = 0.000001;
do {
double FX_ = nx;
double FY_ = ny;
switch (t) {
case TransformationType::None:
nx = FX_;
ny = FY_;
break;
case TransformationType::Translate:
nx = FX_ + m_affine.m_dx;
ny = FY_ + m_affine.m_dy;
break;
case TransformationType::Scale:
nx = m_affine.m_11 * FX_ + m_affine.m_dx;
ny = m_affine.m_22 * FY_ + m_affine.m_dy;
break;
case TransformationType::Rotate:
case TransformationType::Shear:
case TransformationType::Project:
nx = m_affine.m_11 * FX_ + m_affine.m_21 * FY_ + m_affine.m_dx;
ny = m_affine.m_12 * FX_ + m_affine.m_22 * FY_ + m_affine.m_dy;
if (t == TransformationType::Project) {
double w = (m_13 * FX_ + m_23 * FY_ + m_33);
if (w < nearClip) {
w = nearClip;
}
w = 1. / w;
nx *= w;
ny *= w;
}
}
} while (0);
}
#ifndef NO_QT_SUPPORT
QTransform Transform::toQTransform(const Transform& transform)
{
return QTransform(transform.m_affine.m_11, transform.m_affine.m_12, transform.m_13, transform.m_affine.m_21, transform.m_affine.m_22,
transform.m_23, transform.m_affine.m_dx, transform.m_affine.m_dy, transform.m_33);
}
Transform Transform::fromQTransform(const QTransform& transform)
{
return Transform(transform.m11(), transform.m12(), transform.m13(), transform.m21(), transform.m22(), transform.m23(),
transform.m31(), transform.m32(), transform.m33());
}
#endif
}

View file

@ -23,24 +23,150 @@
#ifndef MU_TRANSFORM_H
#define MU_TRANSFORM_H
#include <QTransform>
#include "matrix.h"
#include "geometry.h"
namespace mu {
//! NOTE Temporary implementation
class Transform : public QTransform
class PainterPath;
class Transform
{
public:
enum class TransformationType {
None = 0x00,
Translate = 0x01,
Scale = 0x02,
Rotate = 0x04,
Shear = 0x08,
Project = 0x10
};
Transform() = default;
Transform(const QTransform& t)
: QTransform(t) {}
PointF map(const PointF& p) const { return PointF::fromQPointF(QTransform::map(p.toQPointF())); }
LineF map(const LineF& p) const { return LineF::fromQLineF(QTransform::map(p.toQLineF())); }
PainterPath map(const PainterPath& p) const { return QTransform::map(p); }
Transform(double h11, double h12, double h21, double h22, double dx, double dy);
Transform inverted(bool* invertible = nullptr) const { return Transform(QTransform::inverted(invertible)); }
Transform operator*(const Transform& m) const;
Transform& operator*=(const Transform& o);
Transform& operator*=(double num);
Transform& operator/=(double div);
double m11() const { return m_affine.m_11; }
double m12() const { return m_affine.m_12; }
double m13() const { return m_13; }
double m21() const { return m_affine.m_21; }
double m22() const { return m_affine.m_22; }
double m23() const { return m_23; }
double m31() const { return m_affine.m_dx; }
double m32() const { return m_affine.m_dy; }
double m33() const { return m_33; }
double dx() const { return m_affine.m_dx; }
double dy() const { return m_affine.m_dy; }
PointF map(const PointF& p) const;
LineF map(const LineF& l) const;
PainterPath map(const PainterPath& path) const;
const Matrix& toAffine() const { return m_affine; }
void setMatrix(double m11, double m12, double m13, double m21, double m22, double m23, double m31, double m32, double m33);
void reset();
Transform& rotate(double a);
Transform& rotateRadians(double a);
Transform& translate(double dx, double dy);
Transform& scale(double sx, double sy);
Transform& shear(double sh, double sv);
Transform inverted() const;
#ifndef NO_QT_SUPPORT
static QTransform toQTransform(const Transform& transform);
static Transform fromQTransform(const QTransform& transform);
#endif
private:
Transform(double h11, double h12, double h13, double h21, double h22, double h23, double h31, double h32, double h33);
inline double determinant() const
{
return m_affine.m_11 * (m_33 * m_affine.m_22 - m_affine.m_dy * m_23)
- m_affine.m_21 * (m_33 * m_affine.m_12 - m_affine.m_dy * m_13) + m_affine.m_dx
* (m_23 * m_affine.m_12 - m_affine.m_22 * m_13);
}
Transform adjoint() const;
Transform::TransformationType type() const;
inline Transform::TransformationType inline_type() const
{
if (m_dirty == TransformationType::None) {
return m_type;
}
return type();
}
void mapElement(double& nx, double& ny, TransformationType t) const;
#ifdef DEBUG
static void nanWarning(const std::string& func)
{
LOGW() << "Transform:: " << func << " with NaN called";
}
#endif
Matrix m_affine { 1, 0, 0, 1, 0, 0 };
double m_13 = 0;
double m_23 = 0;
double m_33 = 1;
mutable TransformationType m_type = TransformationType::None;
mutable TransformationType m_dirty = TransformationType::None;
};
inline Transform& Transform::operator*=(double num)
{
if (num == 1.) {
return *this;
}
m_affine.m_11 *= num;
m_affine.m_12 *= num;
m_13 *= num;
m_affine.m_21 *= num;
m_affine.m_22 *= num;
m_23 *= num;
m_affine.m_dx *= num;
m_affine.m_dy *= num;
m_33 *= num;
if (m_dirty < TransformationType::Scale) {
m_dirty = TransformationType::Scale;
}
return *this;
}
inline Transform& Transform::operator/=(double div)
{
if (div == 0) {
return *this;
}
div = 1 / div;
return operator*=(div);
}
inline PointF operator*(const PointF& p, const Transform& m)
{
return m.map(p);
}
inline Transform operator/(const Transform& a, double n)
{
Transform t(a);
t /= n;
return t;
}
}
#endif // MU_TRANSFORM_H

View file

@ -140,7 +140,7 @@ static bool isEqual(const Font& f1, const Font& f2)
return true;
}
static bool isEqual(const QTransform& t1, const QTransform& t2, double tolerance)
static bool isEqual(const Transform& t1, const Transform& t2, double tolerance)
{
if (!isEqual(t1.m11(), t2.m11(), tolerance)) {
return false;
@ -210,15 +210,15 @@ static bool isEqual(const DrawData::State& s1, const DrawData::State& s2, DrawCo
return true;
}
static bool isEqual(const QPainterPath& v1, const QPainterPath& v2, double tolerance)
static bool isEqual(const PainterPath& v1, const PainterPath& v2, double tolerance)
{
if (v1.elementCount() != v2.elementCount()) {
return false;
}
for (int i = 0; i < v1.elementCount(); ++i) {
QPainterPath::Element e1 = v1.elementAt(i);
QPainterPath::Element e2 = v2.elementAt(i);
PainterPath::Element e1 = v1.elementAt(i);
PainterPath::Element e2 = v2.elementAt(i);
if (e1.type != e2.type) {
return false;

View file

@ -27,6 +27,10 @@
#include <QJsonValue>
#include <QJsonParseError>
#ifndef NO_QT_SUPPORT
#include <QPainterPath>
#endif
#include "realfn.h"
#include "log.h"
@ -91,14 +95,14 @@ static void fromObj(const QJsonObject& obj, Font& font)
font.setItalic(obj["italic"].toBool());
}
static QJsonArray toArr(const QTransform& t)
static QJsonArray toArr(const Transform& t)
{
return QJsonArray({ rtoi(t.m11()), rtoi(t.m12()), rtoi(t.m13()),
rtoi(t.m21()), rtoi(t.m22()), rtoi(t.m23()),
rtoi(t.m31()), rtoi(t.m32()), rtoi(t.m33()) });
}
static void fromArr(const QJsonArray& arr, QTransform& t)
static void fromArr(const QJsonArray& arr, Transform& t)
{
IF_ASSERT_FAILED(arr.size() == 9) {
return;
@ -171,6 +175,7 @@ static void fromObj(const QJsonObject& obj, DrawData::State& st)
st.compositionMode = static_cast<CompositionMode>(obj["compositionMode"].toInt());
}
#ifndef NO_QT_SUPPORT
static QJsonObject toObj(const QPainterPath& path)
{
QJsonObject obj;
@ -185,6 +190,22 @@ static QJsonObject toObj(const QPainterPath& path)
return obj;
}
#endif
static QJsonObject toObj(const PainterPath& path)
{
QJsonObject obj;
obj["fillRule"] = static_cast<int>(path.fillRule());
QJsonArray elsArr;
for (int i = 0; i < path.elementCount(); ++i) {
PainterPath::Element e = path.elementAt(i);
elsArr.append(QJsonArray({ static_cast<int>(e.type), rtoi(e.x), rtoi(e.y) }));
}
obj["elements"] = elsArr;
return obj;
}
static QJsonObject toObj(const DrawPath& path)
{
QJsonObject obj;
@ -195,6 +216,7 @@ static QJsonObject toObj(const DrawPath& path)
return obj;
}
#ifndef NO_QT_SUPPORT
static void fromObj(const QJsonObject& obj, QPainterPath& path)
{
path.setFillRule(static_cast<Qt::FillRule>(obj["fillRule"].toInt()));
@ -250,6 +272,63 @@ static void fromObj(const QJsonObject& obj, QPainterPath& path)
}
}
#endif
static void fromObj(const QJsonObject& obj, PainterPath& path)
{
path.setFillRule(static_cast<PainterPath::FillRule>(obj["fillRule"].toInt()));
QJsonArray elsArr = obj["elements"].toArray();
std::vector<PainterPath::Element> curveEls;
for (const QJsonValue elVal : elsArr) {
QJsonArray elArr = elVal.toArray();
IF_ASSERT_FAILED(elArr.size() == 3) {
continue;
}
PainterPath::ElementType type = static_cast<PainterPath::ElementType>(elArr.at(0).toInt());
qreal x = itor(elArr.at(1).toInt());
qreal y = itor(elArr.at(2).toInt());
switch (type) {
case PainterPath::ElementType::MoveToElement: {
path.moveTo(x, y);
} break;
case PainterPath::ElementType::LineToElement: {
path.lineTo(x, y);
} break;
case PainterPath::ElementType::CurveToElement: {
IF_ASSERT_FAILED(curveEls.empty()) {
continue;
}
PainterPath::Element e;
e.type = type;
e.x = x;
e.y = y;
curveEls.push_back(std::move(e));
} break;
case PainterPath::ElementType::CurveToDataElement: {
if (curveEls.size() == 1) { // only CurveToElement
PainterPath::Element e;
e.type = type;
e.x = x;
e.y = y;
curveEls.push_back(std::move(e));
continue;
}
IF_ASSERT_FAILED(curveEls.size() == 2) { // must be CurveToElement and one CurveToDataElement
curveEls.clear();
continue;
}
path.cubicTo(curveEls.at(0).x, curveEls.at(0).y, curveEls.at(1).x, curveEls.at(1).y, x, y);
curveEls.clear();
} break;
}
}
}
static void fromObj(const QJsonObject& obj, DrawPath& path)
{
fromObj(obj["path"].toObject(), path.path);
@ -456,3 +535,16 @@ mu::RetVal<DrawDataPtr> DrawBufferJson::fromJson(const QByteArray& json)
return RetVal<DrawDataPtr>::make_ok(buf);
}
QJsonObject DrawBufferJson::painterPathToObj(const PainterPath& path)
{
return toObj(path);
}
#ifndef NO_QT_SUPPORT
void DrawBufferJson::qPainterPathfromObj(const QJsonObject& obj, QPainterPath& path)
{
fromObj(obj, path);
}
#endif

View file

@ -23,6 +23,7 @@
#define MU_DRAW_DRAWJSON_H
#include <QByteArray>
#include <QJsonObject>
#include "../buffereddrawtypes.h"
#include "retval.h"
@ -33,6 +34,11 @@ public:
static QByteArray toJson(const DrawData& buf);
static RetVal<DrawDataPtr> fromJson(const QByteArray& json);
static QJsonObject painterPathToObj(const PainterPath& path);
#ifndef NO_QT_SUPPORT
static void qPainterPathfromObj(const QJsonObject& obj, QPainterPath& path);
#endif
};
}
#endif // MU_DRAW_DRAWJSON_H

View file

@ -25,6 +25,7 @@
#include "element.h"
#include "bracketItem.h"
#include "draw/painterpath.h"
namespace Ms {
class MuseScoreView;

View file

@ -173,18 +173,18 @@ void ChordLine::read(XmlReader& e)
qreal x = e.doubleAttribute("x");
qreal y = e.doubleAttribute("y");
switch (PainterPath::ElementType(type)) {
case PainterPath::MoveToElement:
case PainterPath::ElementType::MoveToElement:
path.moveTo(x, y);
break;
case PainterPath::LineToElement:
case PainterPath::ElementType::LineToElement:
path.lineTo(x, y);
break;
case PainterPath::CurveToElement:
case PainterPath::ElementType::CurveToElement:
curveTo.rx() = x;
curveTo.ry() = y;
state = 1;
break;
case PainterPath::CurveToDataElement:
case PainterPath::ElementType::CurveToDataElement:
if (state == 1) {
p1.rx() = x;
p1.ry() = y;
@ -339,15 +339,15 @@ void ChordLine::editDrag(EditData& ed)
y += dy;
}
switch (e.type) {
case PainterPath::CurveToDataElement:
case PainterPath::ElementType::CurveToDataElement:
break;
case PainterPath::MoveToElement:
case PainterPath::ElementType::MoveToElement:
p.moveTo(x, y);
break;
case PainterPath::LineToElement:
case PainterPath::ElementType::LineToElement:
p.lineTo(x, y);
break;
case PainterPath::CurveToElement:
case PainterPath::ElementType::CurveToElement:
{
qreal x2 = path.elementAt(i + 1).x;
qreal y2 = path.elementAt(i + 1).y;

View file

@ -24,6 +24,7 @@
#define __CHORDLINE_H__
#include "element.h"
#include "draw/painterpath.h"
namespace Ms {
class Chord;

View file

@ -24,6 +24,7 @@
#define __LAYOUTBREAK_H__
#include "element.h"
#include "draw/painterpath.h"
namespace Ms {
//---------------------------------------------------------

View file

@ -25,6 +25,7 @@
#include "spanner.h"
#include "mscore.h"
#include "draw/painterpath.h"
namespace Ms {
//---------------------------------------------------------

View file

@ -24,6 +24,7 @@
#define __SPACER_H__
#include "element.h"
#include "draw/painterpath.h"
namespace Ms {
//---------------------------------------------------------

View file

@ -25,6 +25,7 @@
#include "element.h"
#include "instrument.h"
#include "draw/painterpath.h"
namespace Ms {
enum class StaffStateType : char {

View file

@ -3374,7 +3374,7 @@ void TextBase::drawEditMode(mu::draw::Painter* p, EditData& ed)
p->drawRect(cursor->cursorRect());
}
QTransform transform = p->worldTransform();
Transform transform = p->worldTransform();
p->translate(-pos);
p->setPen(Pen(Qt::lightGray, 4.0 / transform.m11())); // 4 pixel pen size
p->setBrush(BrushStyle::NoBrush);

View file

@ -25,6 +25,7 @@
#include "durationtype.h"
#include "symbol.h"
#include "draw/painterpath.h"
namespace Ms {
class Chord;

View file

@ -62,6 +62,7 @@
#include "instrumentsconverter.h"
#include "draw/pen.h"
#include "draw/painterpath.h"
using namespace mu::notation;
@ -1744,8 +1745,8 @@ void NotationInteraction::drawSelectionRange(draw::Painter* painter)
std::vector<RectF> rangeArea = m_selection->range()->boundingArea();
for (const RectF& rect: rangeArea) {
QPainterPath path;
path.addRoundedRect(rect.toQRectF(), 6, 6);
mu::PainterPath path;
path.addRoundedRect(rect, 6, 6);
QColor fillColor = selectionColor;
fillColor.setAlpha(10);

View file

@ -361,7 +361,7 @@ void NotationPaintView::paint(QPainter* qp)
RectF rect(0.0, 0.0, width(), height());
paintBackground(rect, painter);
painter->setWorldTransform(m_matrix);
painter->setWorldTransform(mu::Transform::fromQTransform(m_matrix));
notation()->paint(painter, toLogical(rect.toQRect()));

View file

@ -123,7 +123,7 @@ void KeyCanvas::paintEvent(QPaintEvent*)
qreal x = 3;
qreal w = ww - 6;
painter.setWorldTransform(_matrix);
painter.setWorldTransform(mu::Transform::fromQTransform(_matrix));
QRectF r = imatrix.mapRect(QRectF(x, y, w, wh));