removed QTransform and QPainterPath
This commit is contained in:
parent
e7a56c21e4
commit
f322676d4d
33 changed files with 2109 additions and 114 deletions
|
@ -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());
|
||||
|
|
106
src/engraving/draw/bezier.cpp
Normal file
106
src/engraving/draw/bezier.cpp
Normal 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);
|
||||
}
|
||||
}
|
55
src/engraving/draw/bezier.h
Normal file
55
src/engraving/draw/bezier.h
Normal 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
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
#ifndef MU_DRAW_DRAWTYPES_H
|
||||
#define MU_DRAW_DRAWTYPES_H
|
||||
|
||||
#include <QPainterPath>
|
||||
#include <QVariant>
|
||||
#include "geometry.h"
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
84
src/engraving/draw/matrix.h
Normal file
84
src/engraving/draw/matrix.h
Normal 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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
653
src/engraving/draw/painterpath.cpp
Normal file
653
src/engraving/draw/painterpath.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
169
src/engraving/draw/painterpath.h
Normal file
169
src/engraving/draw/painterpath.h
Normal 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
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
724
src/engraving/draw/transform.cpp
Normal file
724
src/engraving/draw/transform.cpp
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "element.h"
|
||||
#include "bracketItem.h"
|
||||
#include "draw/painterpath.h"
|
||||
|
||||
namespace Ms {
|
||||
class MuseScoreView;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#define __CHORDLINE_H__
|
||||
|
||||
#include "element.h"
|
||||
#include "draw/painterpath.h"
|
||||
|
||||
namespace Ms {
|
||||
class Chord;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#define __LAYOUTBREAK_H__
|
||||
|
||||
#include "element.h"
|
||||
#include "draw/painterpath.h"
|
||||
|
||||
namespace Ms {
|
||||
//---------------------------------------------------------
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "spanner.h"
|
||||
#include "mscore.h"
|
||||
#include "draw/painterpath.h"
|
||||
|
||||
namespace Ms {
|
||||
//---------------------------------------------------------
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#define __SPACER_H__
|
||||
|
||||
#include "element.h"
|
||||
#include "draw/painterpath.h"
|
||||
|
||||
namespace Ms {
|
||||
//---------------------------------------------------------
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "element.h"
|
||||
#include "instrument.h"
|
||||
#include "draw/painterpath.h"
|
||||
|
||||
namespace Ms {
|
||||
enum class StaffStateType : char {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "durationtype.h"
|
||||
#include "symbol.h"
|
||||
#include "draw/painterpath.h"
|
||||
|
||||
namespace Ms {
|
||||
class Chord;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()));
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
Loading…
Reference in a new issue