diff options
Diffstat (limited to 'src/gui/painting')
-rw-r--r-- | src/gui/painting/painting.pri | 3 | ||||
-rw-r--r-- | src/gui/painting/qbrush.cpp | 2 | ||||
-rw-r--r-- | src/gui/painting/qcolor.cpp | 33 | ||||
-rw-r--r-- | src/gui/painting/qcolor.h | 16 | ||||
-rw-r--r-- | src/gui/painting/qdrawhelper.cpp | 645 | ||||
-rw-r--r-- | src/gui/painting/qemulationpaintengine.cpp | 84 | ||||
-rw-r--r-- | src/gui/painting/qgrayraster.c | 809 | ||||
-rw-r--r-- | src/gui/painting/qpagedpaintdevice.cpp | 12 | ||||
-rw-r--r-- | src/gui/painting/qpagedpaintdevice.h | 2 | ||||
-rw-r--r-- | src/gui/painting/qpaintengine.cpp | 8 | ||||
-rw-r--r-- | src/gui/painting/qpaintengine_p.h | 25 | ||||
-rw-r--r-- | src/gui/painting/qpdf.cpp | 305 | ||||
-rw-r--r-- | src/gui/painting/qpdf.qrc | 7 | ||||
-rw-r--r-- | src/gui/painting/qpdf_p.h | 11 | ||||
-rw-r--r-- | src/gui/painting/qpdfa_metadata.xml | 16 | ||||
-rw-r--r-- | src/gui/painting/qpdfwriter.cpp | 31 | ||||
-rw-r--r-- | src/gui/painting/qpdfwriter.h | 3 | ||||
-rw-r--r-- | src/gui/painting/qplatformbackingstore.cpp | 34 | ||||
-rw-r--r-- | src/gui/painting/qplatformbackingstore.h | 3 | ||||
-rw-r--r-- | src/gui/painting/qpolygon.cpp | 43 | ||||
-rw-r--r-- | src/gui/painting/qpolygon.h | 4 |
21 files changed, 1278 insertions, 818 deletions
diff --git a/src/gui/painting/painting.pri b/src/gui/painting/painting.pri index 63e345545c..9ff0b5f5e5 100644 --- a/src/gui/painting/painting.pri +++ b/src/gui/painting/painting.pri @@ -99,6 +99,9 @@ SOURCES += \ painting/qplatformbackingstore.cpp \ painting/qpathsimplifier.cpp +RESOURCES += \ + painting/qpdf.qrc + darwin { HEADERS += painting/qcoregraphics_p.h SOURCES += painting/qcoregraphics.mm diff --git a/src/gui/painting/qbrush.cpp b/src/gui/painting/qbrush.cpp index cc3ee76f0d..5c13308d94 100644 --- a/src/gui/painting/qbrush.cpp +++ b/src/gui/painting/qbrush.cpp @@ -100,7 +100,7 @@ const uchar *qt_patternForBrush(int brushStyle, bool invert) return pat_tbl[brushStyle - Qt::Dense1Pattern][invert]; } -QPixmap qt_pixmapForBrush(int brushStyle, bool invert) +Q_GUI_EXPORT QPixmap qt_pixmapForBrush(int brushStyle, bool invert) { QPixmap pm; diff --git a/src/gui/painting/qcolor.cpp b/src/gui/painting/qcolor.cpp index 9e1785c11d..855f245396 100644 --- a/src/gui/painting/qcolor.cpp +++ b/src/gui/painting/qcolor.cpp @@ -79,7 +79,7 @@ static inline int hex2int(char s) return h < 0 ? h : (h << 4) | h; } -static bool get_hex_rgb(const char *name, int len, QRgb *rgb) +static bool get_hex_rgb(const char *name, size_t len, QRgb *rgb) { if (name[0] != '#') return false; @@ -124,12 +124,12 @@ bool qt_get_hex_rgb(const char *name, QRgb *rgb) return get_hex_rgb(name, qstrlen(name), rgb); } -static bool get_hex_rgb(const QChar *str, int len, QRgb *rgb) +static bool get_hex_rgb(const QChar *str, size_t len, QRgb *rgb) { if (len > 13) return false; char tmp[16]; - for (int i = 0; i < len; ++i) + for (size_t i = 0; i < len; ++i) tmp[i] = str[i].toLatin1(); tmp[len] = 0; return get_hex_rgb(tmp, len, rgb); @@ -858,6 +858,7 @@ QString QColor::name(NameFormat format) const return QString(); } +#if QT_STRINGVIEW_LEVEL < 2 /*! Sets the RGB value of this QColor to \a name, which may be in one of these formats: @@ -883,6 +884,17 @@ QString QColor::name(NameFormat format) const void QColor::setNamedColor(const QString &name) { + setColorFromString(qToStringViewIgnoringNull(name)); +} +#endif + +/*! + \overload + \since 5.10 +*/ + +void QColor::setNamedColor(QStringView name) +{ setColorFromString(name); } @@ -896,6 +908,7 @@ void QColor::setNamedColor(QLatin1String name) setColorFromString(name); } +#if QT_STRINGVIEW_LEVEL < 2 /*! \since 4.7 @@ -909,7 +922,17 @@ void QColor::setNamedColor(QLatin1String name) */ bool QColor::isValidColor(const QString &name) { - return !name.isEmpty() && QColor().setColorFromString(name); + return isValidColor(qToStringViewIgnoringNull(name)); +} +#endif + +/*! + \overload + \since 5.10 +*/ +bool QColor::isValidColor(QStringView name) Q_DECL_NOTHROW +{ + return name.size() && QColor().setColorFromString(name); } /*! @@ -922,7 +945,7 @@ bool QColor::isValidColor(QLatin1String name) Q_DECL_NOTHROW } template <typename String> -bool QColor::setColorFromString(const String &name) +bool QColor::setColorFromString(String name) { if (!name.size()) { invalidate(); diff --git a/src/gui/painting/qcolor.h b/src/gui/painting/qcolor.h index 83a93e25ea..0c5ebcbda9 100644 --- a/src/gui/painting/qcolor.h +++ b/src/gui/painting/qcolor.h @@ -72,7 +72,10 @@ public: inline QColor(int r, int g, int b, int a = 255); QColor(QRgb rgb) Q_DECL_NOTHROW; QColor(QRgba64 rgba64) Q_DECL_NOTHROW; +#if QT_STRINGVIEW_LEVEL < 2 inline QColor(const QString& name); +#endif + explicit inline QColor(QStringView name); inline QColor(const char *aname) : QColor(QLatin1String(aname)) {} inline QColor(QLatin1String name); QColor(Spec spec) Q_DECL_NOTHROW; @@ -95,7 +98,10 @@ public: QString name() const; QString name(NameFormat format) const; +#if QT_STRINGVIEW_LEVEL < 2 void setNamedColor(const QString& name); +#endif + void setNamedColor(QStringView name); void setNamedColor(QLatin1String name); static QStringList colorNames(); @@ -221,14 +227,17 @@ public: operator QVariant() const; +#if QT_STRINGVIEW_LEVEL < 2 static bool isValidColor(const QString &name); +#endif + static bool isValidColor(QStringView) Q_DECL_NOTHROW; static bool isValidColor(QLatin1String) Q_DECL_NOTHROW; private: void invalidate() Q_DECL_NOTHROW; template <typename String> - bool setColorFromString(const String &name); + bool setColorFromString(String name); Spec cspec; union { @@ -280,8 +289,13 @@ inline QColor::QColor(int r, int g, int b, int a) inline QColor::QColor(QLatin1String aname) { setNamedColor(aname); } +inline QColor::QColor(QStringView aname) +{ setNamedColor(aname); } + +#if QT_STRINGVIEW_LEVEL < 2 inline QColor::QColor(const QString& aname) { setNamedColor(aname); } +#endif #if QT_VERSION < QT_VERSION_CHECK(6,0,0) inline QColor::QColor(const QColor &acolor) Q_DECL_NOTHROW diff --git a/src/gui/painting/qdrawhelper.cpp b/src/gui/painting/qdrawhelper.cpp index cb3e7523a8..b329f2c915 100644 --- a/src/gui/painting/qdrawhelper.cpp +++ b/src/gui/painting/qdrawhelper.cpp @@ -2657,6 +2657,241 @@ static const uint * QT_FASTCALL fetchTransformedBilinearARGB32PM(uint *buffer, c return buffer; } +template<TextureBlendType blendType, QPixelLayout::BPP bpp> +static void QT_FASTCALL fetchTransformedBilinear_simple_upscale_helper(uint *b, uint *end, const QTextureData &image, + int &fx, int &fy, int fdx, int /*fdy*/) +{ + const QPixelLayout *layout = &qPixelLayouts[image.format]; + const QVector<QRgb> *clut = image.colorTable; + Q_ASSERT(bpp == QPixelLayout::BPPNone || bpp == layout->bpp); + // When templated 'fetch' should be inlined at compile time: + const FetchPixelsFunc fetch = (bpp == QPixelLayout::BPPNone) ? qFetchPixels[layout->bpp] : fetchPixels<bpp>; + const ConvertFunc convertToARGB32PM = layout->convertToARGB32PM; + + int y1 = (fy >> 16); + int y2; + fetchTransformedBilinear_pixelBounds<blendType>(image.height, image.y1, image.y2 - 1, y1, y2); + const uchar *s1 = image.scanLine(y1); + const uchar *s2 = image.scanLine(y2); + + int disty = (fy & 0x0000ffff) >> 8; + int idisty = 256 - disty; + int x = fx >> 16; + int length = end - b; + + // The idea is first to do the interpolation between the row s1 and the row s2 + // into an intermediate buffer, then we interpolate between two pixel of this buffer. + // +1 for the last pixel to interpolate with, and +1 for rounding errors. + uint buf1[buffer_size + 2]; + uint buf2[buffer_size + 2]; + const uint *ptr1; + const uint *ptr2; + + int count = (qint64(length) * fdx + fixed_scale - 1) / fixed_scale + 2; + Q_ASSERT(count <= buffer_size + 2); //length is supposed to be <= buffer_size and data->m11 < 1 in this case + + if (blendType == BlendTransformedBilinearTiled) { + x %= image.width; + if (x < 0) + x += image.width; + int len1 = qMin(count, image.width - x); + int len2 = qMin(x, count - len1); + + ptr1 = fetch(buf1, s1, x, len1); + ptr1 = convertToARGB32PM(buf1, ptr1, len1, clut, 0); + ptr2 = fetch(buf2, s2, x, len1); + ptr2 = convertToARGB32PM(buf2, ptr2, len1, clut, 0); + for (int i = 0; i < len1; ++i) { + uint t = ptr1[i]; + uint b = ptr2[i]; + buf1[i] = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; + buf2[i] = ((((t >> 8) & 0xff00ff) * idisty + ((b >> 8) & 0xff00ff) * disty) >> 8) & 0xff00ff; + } + + if (len2) { + ptr1 = fetch(buf1 + len1, s1, 0, len2); + ptr1 = convertToARGB32PM(buf1 + len1, ptr1, len2, clut, 0); + ptr2 = fetch(buf2 + len1, s2, 0, len2); + ptr2 = convertToARGB32PM(buf2 + len1, ptr2, len2, clut, 0); + for (int i = 0; i < len2; ++i) { + uint t = ptr1[i]; + uint b = ptr2[i]; + buf1[i + len1] = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; + buf2[i + len1] = ((((t >> 8) & 0xff00ff) * idisty + ((b >> 8) & 0xff00ff) * disty) >> 8) & 0xff00ff; + } + } + for (int i = image.width; i < count; ++i) { + buf1[i] = buf1[i - image.width]; + buf2[i] = buf2[i - image.width]; + } + } else { + int start = qMax(x, image.x1); + int end = qMin(x + count, image.x2); + int len = qMax(1, end - start); + int leading = start - x; + + ptr1 = fetch(buf1 + leading, s1, start, len); + ptr1 = convertToARGB32PM(buf1 + leading, ptr1, len, clut, 0); + ptr2 = fetch(buf2 + leading, s2, start, len); + ptr2 = convertToARGB32PM(buf2 + leading, ptr2, len, clut, 0); + + for (int i = 0; i < len; ++i) { + uint t = ptr1[i]; + uint b = ptr2[i]; + buf1[i + leading] = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; + buf2[i + leading] = ((((t >> 8) & 0xff00ff) * idisty + ((b >> 8) & 0xff00ff) * disty) >> 8) & 0xff00ff; + } + + for (int i = 0; i < leading; ++i) { + buf1[i] = buf1[leading]; + buf2[i] = buf2[leading]; + } + for (int i = leading + len; i < count; ++i) { + buf1[i] = buf1[i - 1]; + buf2[i] = buf2[i - 1]; + } + } + + // Now interpolate the values from the intermediate_buffer to get the final result. + fx &= fixed_scale - 1; + Q_ASSERT((fx >> 16) == 0); + while (b < end) { + int x1 = (fx >> 16); + int x2 = x1 + 1; + Q_ASSERT(x1 >= 0); + Q_ASSERT(x2 < count); + + int distx = (fx & 0x0000ffff) >> 8; + int idistx = 256 - distx; + int rb = ((buf1[x1] * idistx + buf1[x2] * distx) >> 8) & 0xff00ff; + int ag = (buf2[x1] * idistx + buf2[x2] * distx) & 0xff00ff00; + *b++ = rb | ag; + fx += fdx; + } +} + + +typedef void (QT_FASTCALL *BilinearFastTransformFetcher)(uint *buf1, uint *buf2, const int len, const QTextureData &image, + int fx, int fy, const int fdx, const int fdy); + +template<TextureBlendType blendType, QPixelLayout::BPP bpp> +static void QT_FASTCALL fetchTransformedBilinear_fetcher(uint *buf1, uint *buf2, const int len, const QTextureData &image, + int fx, int fy, const int fdx, const int fdy) +{ + const QPixelLayout &layout = qPixelLayouts[image.format]; + Q_ASSERT(bpp == QPixelLayout::BPPNone || bpp == layout.bpp); + // When templated 'fetch1' should be inlined at compile time: + const FetchPixelFunc fetch1 = (bpp == QPixelLayout::BPPNone) ? qFetchPixel[layout.bpp] : fetchPixel<bpp>; + if (fdy == 0) { + int y1 = (fy >> 16); + int y2; + fetchTransformedBilinear_pixelBounds<blendType>(image.height, image.y1, image.y2 - 1, y1, y2); + const uchar *s1 = image.scanLine(y1); + const uchar *s2 = image.scanLine(y2); + + int i = 0; + if (blendType == BlendTransformedBilinear) { + for (; i < len; ++i) { + int x1 = (fx >> 16); + int x2; + fetchTransformedBilinear_pixelBounds<blendType>(image.width, image.x1, image.x2 - 1, x1, x2); + if (x1 != x2) + break; + buf1[i * 2 + 0] = buf1[i * 2 + 1] = fetch1(s1, x1); + buf2[i * 2 + 0] = buf2[i * 2 + 1] = fetch1(s2, x1); + fx += fdx; + } + int fastLen = len; + if (fdx > 0) + fastLen = qMin(fastLen, int((qint64(image.x2 - 1) * fixed_scale - fx) / fdx)); + else if (fdx < 0) + fastLen = qMin(fastLen, int((qint64(image.x1) * fixed_scale - fx) / fdx)); + + for (; i < fastLen; ++i) { + int x = (fx >> 16); + buf1[i * 2 + 0] = fetch1(s1, x); + buf1[i * 2 + 1] = fetch1(s1, x + 1); + buf2[i * 2 + 0] = fetch1(s2, x); + buf2[i * 2 + 1] = fetch1(s2, x + 1); + fx += fdx; + } + } + + for (; i < len; ++i) { + int x1 = (fx >> 16); + int x2; + fetchTransformedBilinear_pixelBounds<blendType>(image.width, image.x1, image.x2 - 1, x1, x2); + buf1[i * 2 + 0] = fetch1(s1, x1); + buf1[i * 2 + 1] = fetch1(s1, x2); + buf2[i * 2 + 0] = fetch1(s2, x1); + buf2[i * 2 + 1] = fetch1(s2, x2); + fx += fdx; + } + } else { + int i = 0; + if (blendType == BlendTransformedBilinear) { + for (; i < len; ++i) { + int x1 = (fx >> 16); + int x2; + int y1 = (fy >> 16); + int y2; + fetchTransformedBilinear_pixelBounds<blendType>(image.width, image.x1, image.x2 - 1, x1, x2); + fetchTransformedBilinear_pixelBounds<blendType>(image.height, image.y1, image.y2 - 1, y1, y2); + if (x1 != x2 && y1 != y2) + break; + const uchar *s1 = image.scanLine(y1); + const uchar *s2 = image.scanLine(y2); + buf1[i * 2 + 0] = fetch1(s1, x1); + buf1[i * 2 + 1] = fetch1(s1, x2); + buf2[i * 2 + 0] = fetch1(s2, x1); + buf2[i * 2 + 1] = fetch1(s2, x2); + fx += fdx; + fy += fdy; + } + int fastLen = len; + if (fdx > 0) + fastLen = qMin(fastLen, int((qint64(image.x2 - 1) * fixed_scale - fx) / fdx)); + else if (fdx < 0) + fastLen = qMin(fastLen, int((qint64(image.x1) * fixed_scale - fx) / fdx)); + if (fdy > 0) + fastLen = qMin(fastLen, int((qint64(image.y2 - 1) * fixed_scale - fy) / fdy)); + else if (fdy < 0) + fastLen = qMin(fastLen, int((qint64(image.y1) * fixed_scale - fy) / fdy)); + + for (; i < fastLen; ++i) { + int x = (fx >> 16); + int y = (fy >> 16); + const uchar *s1 = image.scanLine(y); + const uchar *s2 = s1 + image.bytesPerLine; + buf1[i * 2 + 0] = fetch1(s1, x); + buf1[i * 2 + 1] = fetch1(s1, x + 1); + buf2[i * 2 + 0] = fetch1(s2, x); + buf2[i * 2 + 1] = fetch1(s2, x + 1); + fx += fdx; + fy += fdy; + } + } + + for (; i < len; ++i) { + int x1 = (fx >> 16); + int x2; + int y1 = (fy >> 16); + int y2; + fetchTransformedBilinear_pixelBounds<blendType>(image.width, image.x1, image.x2 - 1, x1, x2); + fetchTransformedBilinear_pixelBounds<blendType>(image.height, image.y1, image.y2 - 1, y1, y2); + + const uchar *s1 = image.scanLine(y1); + const uchar *s2 = image.scanLine(y2); + buf1[i * 2 + 0] = fetch1(s1, x1); + buf1[i * 2 + 1] = fetch1(s1, x2); + buf2[i * 2 + 0] = fetch1(s2, x1); + buf2[i * 2 + 1] = fetch1(s2, x2); + fx += fdx; + fy += fdy; + } + } +} + // blendType = BlendTransformedBilinear or BlendTransformedBilinearTiled template<TextureBlendType blendType, QPixelLayout::BPP bpp> static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Operator *, @@ -2664,19 +2899,7 @@ static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Oper { const QPixelLayout *layout = &qPixelLayouts[data->texture.format]; const QVector<QRgb> *clut = data->texture.colorTable; - if (bpp != QPixelLayout::BPPNone) // Like this to not ICE on GCC 5.3.1 - Q_ASSERT(layout->bpp == bpp); - // When templated 'fetch' should be inlined at compile time: - const FetchPixelsFunc fetch = (bpp == QPixelLayout::BPPNone) ? qFetchPixels[layout->bpp] : FetchPixelsFunc(fetchPixels<bpp>); - const FetchPixelFunc fetch1 = (bpp == QPixelLayout::BPPNone) ? qFetchPixel[layout->bpp] : FetchPixelFunc(fetchPixel<bpp>); - - int image_width = data->texture.width; - int image_height = data->texture.height; - - int image_x1 = data->texture.x1; - int image_y1 = data->texture.y1; - int image_x2 = data->texture.x2 - 1; - int image_y2 = data->texture.y2 - 1; + Q_ASSERT(bpp == QPixelLayout::BPPNone || layout->bpp == bpp); const qreal cx = x + qreal(0.5); const qreal cy = y + qreal(0.5); @@ -2692,203 +2915,80 @@ static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Oper fx -= half_point; fy -= half_point; - if (fdy == 0) { //simple scale, no rotation - int y1 = (fy >> 16); - int y2; - fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); - const uchar *s1 = data->texture.scanLine(y1); - const uchar *s2 = data->texture.scanLine(y2); - + if (fdy == 0) { // simple scale, no rotation or shear if (fdx <= fixed_scale && fdx > 0) { // scale up on X - int disty = (fy & 0x0000ffff) >> 8; - int idisty = 256 - disty; - int x = fx >> 16; - - // The idea is first to do the interpolation between the row s1 and the row s2 - // into an intermediate buffer, then we interpolate between two pixel of this buffer. - // +1 for the last pixel to interpolate with, and +1 for rounding errors. - uint buf1[buffer_size + 2]; - uint buf2[buffer_size + 2]; - const uint *ptr1; - const uint *ptr2; - - int count = (qint64(length) * fdx + fixed_scale - 1) / fixed_scale + 2; - Q_ASSERT(count <= buffer_size + 2); //length is supposed to be <= buffer_size and data->m11 < 1 in this case - - if (blendType == BlendTransformedBilinearTiled) { - x %= image_width; - if (x < 0) - x += image_width; - int len1 = qMin(count, image_width - x); - int len2 = qMin(x, count - len1); - - ptr1 = fetch(buf1, s1, x, len1); - ptr1 = layout->convertToARGB32PM(buf1, ptr1, len1, clut, 0); - ptr2 = fetch(buf2, s2, x, len1); - ptr2 = layout->convertToARGB32PM(buf2, ptr2, len1, clut, 0); - for (int i = 0; i < len1; ++i) { - uint t = ptr1[i]; - uint b = ptr2[i]; - buf1[i] = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; - buf2[i] = ((((t >> 8) & 0xff00ff) * idisty + ((b >> 8) & 0xff00ff) * disty) >> 8) & 0xff00ff; - } - - if (len2) { - ptr1 = fetch(buf1 + len1, s1, 0, len2); - ptr1 = layout->convertToARGB32PM(buf1 + len1, ptr1, len2, clut, 0); - ptr2 = fetch(buf2 + len1, s2, 0, len2); - ptr2 = layout->convertToARGB32PM(buf2 + len1, ptr2, len2, clut, 0); - for (int i = 0; i < len2; ++i) { - uint t = ptr1[i]; - uint b = ptr2[i]; - buf1[i + len1] = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; - buf2[i + len1] = ((((t >> 8) & 0xff00ff) * idisty + ((b >> 8) & 0xff00ff) * disty) >> 8) & 0xff00ff; - } - } - for (int i = image_width; i < count; ++i) { - buf1[i] = buf1[i - image_width]; - buf2[i] = buf2[i - image_width]; - } - } else { - int start = qMax(x, image_x1); - int end = qMin(x + count, image_x2 + 1); - int len = qMax(1, end - start); - int leading = start - x; - - ptr1 = fetch(buf1 + leading, s1, start, len); - ptr1 = layout->convertToARGB32PM(buf1 + leading, ptr1, len, clut, 0); - ptr2 = fetch(buf2 + leading, s2, start, len); - ptr2 = layout->convertToARGB32PM(buf2 + leading, ptr2, len, clut, 0); - - for (int i = 0; i < len; ++i) { - uint t = ptr1[i]; - uint b = ptr2[i]; - buf1[i + leading] = (((t & 0xff00ff) * idisty + (b & 0xff00ff) * disty) >> 8) & 0xff00ff; - buf2[i + leading] = ((((t >> 8) & 0xff00ff) * idisty + ((b >> 8) & 0xff00ff) * disty) >> 8) & 0xff00ff; - } - - for (int i = 0; i < leading; ++i) { - buf1[i] = buf1[leading]; - buf2[i] = buf2[leading]; - } - for (int i = leading + len; i < count; ++i) { - buf1[i] = buf1[i - 1]; - buf2[i] = buf2[i - 1]; - } - } - - // Now interpolate the values from the intermediate_buffer to get the final result. - fx &= fixed_scale - 1; - Q_ASSERT((fx >> 16) == 0); - for (int i = 0; i < length; ++i) { - int x1 = (fx >> 16); - int x2 = x1 + 1; - Q_ASSERT(x1 >= 0); - Q_ASSERT(x2 < count); - - int distx = (fx & 0x0000ffff) >> 8; - int idistx = 256 - distx; - int rb = ((buf1[x1] * idistx + buf1[x2] * distx) >> 8) & 0xff00ff; - int ag = (buf2[x1] * idistx + buf2[x2] * distx) & 0xff00ff00; - buffer[i] = rb | ag; - fx += fdx; - } + fetchTransformedBilinear_simple_upscale_helper<blendType, bpp>(buffer, buffer + length, data->texture, fx, fy, fdx, fdy); } else { + const BilinearFastTransformFetcher fetcher = fetchTransformedBilinear_fetcher<blendType,bpp>; + uint buf1[buffer_size]; uint buf2[buffer_size]; uint *b = buffer; while (length) { int len = qMin(length, buffer_size / 2); - int fracX = fx; - for (int i = 0; i < len; ++i) { - int x1 = (fx >> 16); - int x2; - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - buf1[i * 2 + 0] = fetch1(s1, x1); - buf1[i * 2 + 1] = fetch1(s1, x2); - buf2[i * 2 + 0] = fetch1(s2, x1); - buf2[i * 2 + 1] = fetch1(s2, x2); - fx += fdx; - } + fetcher(buf1, buf2, len, data->texture, fx, fy, fdx, 0); layout->convertToARGB32PM(buf1, buf1, len * 2, clut, 0); layout->convertToARGB32PM(buf2, buf2, len * 2, clut, 0); if ((fdx < 0 && fdx > -(fixed_scale / 8)) || qAbs(data->m22) < qreal(1./8.)) { // scale up more than 8x int disty = (fy & 0x0000ffff) >> 8; for (int i = 0; i < len; ++i) { - int distx = (fracX & 0x0000ffff) >> 8; + int distx = (fx & 0x0000ffff) >> 8; b[i] = interpolate_4_pixels(buf1 + i * 2, buf2 + i * 2, distx, disty); - fracX += fdx; + fx += fdx; } - } else { //scale down + } else { int disty = ((fy & 0x0000ffff) + 0x0800) >> 12; for (int i = 0; i < len; ++i) { uint tl = buf1[i * 2 + 0]; uint tr = buf1[i * 2 + 1]; uint bl = buf2[i * 2 + 0]; uint br = buf2[i * 2 + 1]; - int distx = ((fracX & 0x0000ffff) + 0x0800) >> 12; + int distx = ((fx & 0x0000ffff) + 0x0800) >> 12; b[i] = interpolate_4_pixels_16(tl, tr, bl, br, distx, disty); - fracX += fdx; + fx += fdx; } } length -= len; b += len; } } - } else { //rotation + } else { // rotation or shear + const BilinearFastTransformFetcher fetcher = fetchTransformedBilinear_fetcher<blendType,bpp>; + uint buf1[buffer_size]; uint buf2[buffer_size]; uint *b = buffer; - while (length) { int len = qMin(length, buffer_size / 2); - int fracX = fx; - int fracY = fy; - for (int i = 0; i < len; ++i) { - int x1 = (fx >> 16); - int x2; - int y1 = (fy >> 16); - int y2; - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); - - const uchar *s1 = data->texture.scanLine(y1); - const uchar *s2 = data->texture.scanLine(y2); - buf1[i * 2 + 0] = fetch1(s1, x1); - buf1[i * 2 + 1] = fetch1(s1, x2); - buf2[i * 2 + 0] = fetch1(s2, x1); - buf2[i * 2 + 1] = fetch1(s2, x2); - fx += fdx; - fy += fdy; - } + fetcher(buf1, buf2, len, data->texture, fx, fy, fdx, fdy); layout->convertToARGB32PM(buf1, buf1, len * 2, clut, 0); layout->convertToARGB32PM(buf2, buf2, len * 2, clut, 0); - if (qAbs(data->m11) < qreal(1./8.) || qAbs(data->m22) < qreal(1./8.) ) { - //if we are zooming more than 8 times, we use 8bit precision for the position. + if (qAbs(data->m11) < qreal(1./8.)|| qAbs(data->m22) < qreal(1./8.)) { + // If we are zooming more than 8 times, we use 8bit precision for the position. for (int i = 0; i < len; ++i) { - int distx = (fracX & 0x0000ffff) >> 8; - int disty = (fracY & 0x0000ffff) >> 8; + int distx = (fx & 0x0000ffff) >> 8; + int disty = (fy & 0x0000ffff) >> 8; b[i] = interpolate_4_pixels(buf1 + i * 2, buf2 + i * 2, distx, disty); - fracX += fdx; - fracY += fdy; + fx += fdx; + fy += fdy; } } else { - //we are zooming less than 8x, use 4bit precision + // We are zooming less than 8x, use 4bit precision for (int i = 0; i < len; ++i) { uint tl = buf1[i * 2 + 0]; uint tr = buf1[i * 2 + 1]; uint bl = buf2[i * 2 + 0]; uint br = buf2[i * 2 + 1]; - int distx = ((fracX & 0x0000ffff) + 0x0800) >> 12; - int disty = ((fracY & 0x0000ffff) + 0x0800) >> 12; + int distx = ((fx & 0x0000ffff) + 0x0800) >> 12; + int disty = ((fy & 0x0000ffff) + 0x0800) >> 12; b[i] = interpolate_4_pixels_16(tl, tr, bl, br, distx, disty); - fracX += fdx; - fracY += fdy; + fx += fdx; + fy += fdy; } } @@ -2897,6 +2997,11 @@ static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Oper } } } else { + // When templated 'fetch' should be inlined at compile time: + const FetchPixelFunc fetch1 = (bpp == QPixelLayout::BPPNone) ? qFetchPixel[layout->bpp] : fetchPixel<bpp>; + + const QTextureData &image = data->texture; + const qreal fdx = data->m11; const qreal fdy = data->m12; const qreal fdw = data->m13; @@ -2927,8 +3032,8 @@ static const uint *QT_FASTCALL fetchTransformedBilinear(uint *buffer, const Oper distxs[i] = int((px - x1) * 256); distys[i] = int((py - y1) * 256); - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + fetchTransformedBilinear_pixelBounds<blendType>(image.width, image.x1, image.x2 - 1, x1, x2); + fetchTransformedBilinear_pixelBounds<blendType>(image.height, image.y1, image.y2 - 1, y1, y2); const uchar *s1 = data->texture.scanLine(y1); const uchar *s2 = data->texture.scanLine(y2); @@ -2969,21 +3074,9 @@ static const QRgba64 *QT_FASTCALL fetchTransformedBilinear64(QRgba64 *buffer, co const QPixelLayout *layout = &qPixelLayouts[data->texture.format]; const QVector<QRgb> *clut = data->texture.colorTable; - int image_width = data->texture.width; - int image_height = data->texture.height; - - int image_x1 = data->texture.x1; - int image_y1 = data->texture.y1; - int image_x2 = data->texture.x2 - 1; - int image_y2 = data->texture.y2 - 1; - const qreal cx = x + qreal(0.5); const qreal cy = y + qreal(0.5); - const qreal fdx = data->m11; - const qreal fdy = data->m12; - const qreal fdw = data->m13; - if (data->fast_matrix) { // The increment pr x in the scanline int fdx = (int)(data->m11 * fixed_scale); @@ -2995,14 +3088,13 @@ static const QRgba64 *QT_FASTCALL fetchTransformedBilinear64(QRgba64 *buffer, co fx -= half_point; fy -= half_point; + const BilinearFastTransformFetcher fetcher = + (layout->bpp == QPixelLayout::BPP32) + ? fetchTransformedBilinear_fetcher<blendType, QPixelLayout::BPP32> + : fetchTransformedBilinear_fetcher<blendType, QPixelLayout::BPPNone>; + if (fdy == 0) { //simple scale, no rotation - int y1 = (fy >> 16); - int y2; - fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); - const uchar *s1 = data->texture.scanLine(y1); - const uchar *s2 = data->texture.scanLine(y2); - FetchPixelFunc fetch = qFetchPixel[layout->bpp]; uint sbuf1[buffer_size]; uint sbuf2[buffer_size]; quint64 buf1[buffer_size]; @@ -3010,84 +3102,19 @@ static const QRgba64 *QT_FASTCALL fetchTransformedBilinear64(QRgba64 *buffer, co QRgba64 *b = buffer; while (length) { int len = qMin(length, buffer_size / 2); - int fracX = fx; - int i = 0; int disty = (fy & 0x0000ffff); #if defined(__SSE2__) const __m128i vdy = _mm_set1_epi16(disty); const __m128i vidy = _mm_set1_epi16(0x10000 - disty); - if (blendType != BlendTransformedBilinearTiled && layout->bpp == QPixelLayout::BPP32) { - for (; i < len; ++i) { - int x1 = (fx >> 16); - int x2; - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - if (x1 != x2) - break; - sbuf1[i * 2 + 0] = sbuf1[i * 2 + 1] = ((const uint*)s1)[x1]; - sbuf2[i * 2 + 0] = sbuf2[i * 2 + 1] = ((const uint*)s2)[x1]; - fx += fdx; - } - int fastLen; - if (fdx > 0) - fastLen = qMin(len, int((image_x2 - (fx >> 16)) / data->m11)); - else - fastLen = qMin(len, int((image_x1 - (fx >> 16)) / data->m11)); - fastLen -= 3; - - const __m128i v_fdx = _mm_set1_epi32(fdx*4); - __m128i v_fx = _mm_setr_epi32(fx, fx + fdx, fx + fdx + fdx, fx + fdx + fdx + fdx); - for (; i < fastLen; i += 4) { - int offset = _mm_extract_epi16(v_fx, 1); - sbuf1[i * 2 + 0] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 1] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 0] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 1] = ((const uint*)s2)[offset + 1]; - offset = _mm_extract_epi16(v_fx, 3); - sbuf1[i * 2 + 2] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 3] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 2] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 3] = ((const uint*)s2)[offset + 1]; - offset = _mm_extract_epi16(v_fx, 5); - sbuf1[i * 2 + 4] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 5] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 4] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 5] = ((const uint*)s2)[offset + 1]; - offset = _mm_extract_epi16(v_fx, 7); - sbuf1[i * 2 + 6] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 7] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 6] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 7] = ((const uint*)s2)[offset + 1]; - v_fx = _mm_add_epi32(v_fx, v_fdx); - } - fx = _mm_cvtsi128_si32(v_fx); - } #endif - for (; i < len; ++i) { - int x1 = (fx >> 16); - int x2; - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - - if (layout->bpp == QPixelLayout::BPP32) { - sbuf1[i * 2 + 0] = ((const uint*)s1)[x1]; - sbuf1[i * 2 + 1] = ((const uint*)s1)[x2]; - sbuf2[i * 2 + 0] = ((const uint*)s2)[x1]; - sbuf2[i * 2 + 1] = ((const uint*)s2)[x2]; - - } else { - sbuf1[i * 2 + 0] = fetch(s1, x1); - sbuf1[i * 2 + 1] = fetch(s1, x2); - sbuf2[i * 2 + 0] = fetch(s2, x1); - sbuf2[i * 2 + 1] = fetch(s2, x2); - } + fetcher(sbuf1, sbuf2, len, data->texture, fx, fy, fdx, fdy); - fx += fdx; - } layout->convertToARGB64PM((QRgba64 *)buf1, sbuf1, len * 2, clut, 0); if (disty) layout->convertToARGB64PM((QRgba64 *)buf2, sbuf2, len * 2, clut, 0); for (int i = 0; i < len; ++i) { - int distx = (fracX & 0x0000ffff); + int distx = (fx & 0x0000ffff); #if defined(__SSE2__) const __m128i vdistx = _mm_shufflelo_epi16(_mm_cvtsi32_si128(distx), _MM_SHUFFLE(0, 0, 0, 0)); const __m128i vidistx = _mm_shufflelo_epi16(_mm_cvtsi32_si128(0x10000 - distx), _MM_SHUFFLE(0, 0, 0, 0)); @@ -3104,13 +3131,12 @@ static const QRgba64 *QT_FASTCALL fetchTransformedBilinear64(QRgba64 *buffer, co #else b[i] = interpolate_4_pixels_rgb64((QRgba64 *)buf1 + i*2, (QRgba64 *)buf2 + i*2, distx, disty); #endif - fracX += fdx; + fx += fdx; } length -= len; b += len; } } else { //rotation - FetchPixelFunc fetch = qFetchPixel[layout->bpp]; uint sbuf1[buffer_size]; uint sbuf2[buffer_size]; quint64 buf1[buffer_size]; @@ -3120,117 +3146,18 @@ static const QRgba64 *QT_FASTCALL fetchTransformedBilinear64(QRgba64 *buffer, co while (b < end) { int len = qMin(length, buffer_size / 2); - int fracX = fx; - int fracY = fy; - int i = 0; -#if defined(__SSE2__) - if (blendType != BlendTransformedBilinearTiled && layout->bpp == QPixelLayout::BPP32) { - for (; i < len; ++i) { - int x1 = (fx >> 16); - int x2; - int y1 = (fy >> 16); - int y2; - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); - if (x1 != x2 && y1 != y2) - break; - const uchar *s1 = data->texture.scanLine(y1); - const uchar *s2 = data->texture.scanLine(y2); - sbuf1[i * 2 + 0] = ((const uint*)s1)[x1]; - sbuf1[i * 2 + 1] = ((const uint*)s1)[x2]; - sbuf2[i * 2 + 0] = ((const uint*)s2)[x1]; - sbuf2[i * 2 + 1] = ((const uint*)s2)[x2]; - fx += fdx; - fy += fdy; - } - int fastLen = len; - if (fdx > 0) - fastLen = qMin(fastLen, int((qint64(image_x2) * fixed_scale - fx) / fdx)); - else if (fdx < 0) - fastLen = qMin(fastLen, int((qint64(image_x1) * fixed_scale - fx) / fdx)); - if (fdy > 0) - fastLen = qMin(fastLen, int((qint64(image_y2) * fixed_scale - fy) / fdy)); - else if (fdy < 0) - fastLen = qMin(fastLen, int((qint64(image_y1) * fixed_scale - fy) / fdy)); - fastLen -= 3; - - const __m128i v_fdx = _mm_set1_epi32(fdx*4); - const __m128i v_fdy = _mm_set1_epi32(fdy*4); - __m128i v_fx = _mm_setr_epi32(fx, fx + fdx, fx + fdx + fdx, fx + fdx + fdx + fdx); - __m128i v_fy = _mm_setr_epi32(fy, fy + fdy, fy + fdy + fdy, fy + fdy + fdy + fdy); - const int bytesPerLine = data->texture.bytesPerLine; - const uchar *s1 = data->texture.imageData; - const uchar *s2 = s1 + bytesPerLine; - const __m128i vbpl = _mm_shufflelo_epi16(_mm_cvtsi32_si128(bytesPerLine/4), _MM_SHUFFLE(0, 0, 0, 0)); - for (; i < fastLen; i += 4) { - const __m128i vy = _mm_packs_epi32(_mm_srai_epi32(v_fy, 16), _mm_setzero_si128()); - __m128i voffset = _mm_unpacklo_epi16(_mm_mullo_epi16(vy, vbpl), _mm_mulhi_epu16(vy, vbpl)); - voffset = _mm_add_epi32(voffset, _mm_srli_epi32(v_fx, 16)); - - int offset = _mm_cvtsi128_si32(voffset); voffset = _mm_srli_si128(voffset, 4); - sbuf1[i * 2 + 0] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 1] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 0] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 1] = ((const uint*)s2)[offset + 1]; - offset = _mm_cvtsi128_si32(voffset); voffset = _mm_srli_si128(voffset, 4); - sbuf1[i * 2 + 2] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 3] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 2] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 3] = ((const uint*)s2)[offset + 1]; - offset = _mm_cvtsi128_si32(voffset); voffset = _mm_srli_si128(voffset, 4); - sbuf1[i * 2 + 4] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 5] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 4] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 5] = ((const uint*)s2)[offset + 1]; - offset = _mm_cvtsi128_si32(voffset); - sbuf1[i * 2 + 6] = ((const uint*)s1)[offset]; - sbuf1[i * 2 + 7] = ((const uint*)s1)[offset + 1]; - sbuf2[i * 2 + 6] = ((const uint*)s2)[offset]; - sbuf2[i * 2 + 7] = ((const uint*)s2)[offset + 1]; - - v_fx = _mm_add_epi32(v_fx, v_fdx); - v_fy = _mm_add_epi32(v_fy, v_fdy); - } - fx = _mm_cvtsi128_si32(v_fx); - fy = _mm_cvtsi128_si32(v_fy); - } -#endif - for (; i < len; ++i) { - int x1 = (fx >> 16); - int x2; - int y1 = (fy >> 16); - int y2; - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); - const uchar *s1 = data->texture.scanLine(y1); - const uchar *s2 = data->texture.scanLine(y2); - - if (layout->bpp == QPixelLayout::BPP32) { - sbuf1[i * 2 + 0] = ((const uint*)s1)[x1]; - sbuf1[i * 2 + 1] = ((const uint*)s1)[x2]; - sbuf2[i * 2 + 0] = ((const uint*)s2)[x1]; - sbuf2[i * 2 + 1] = ((const uint*)s2)[x2]; - - } else { - sbuf1[i * 2 + 0] = fetch(s1, x1); - sbuf1[i * 2 + 1] = fetch(s1, x2); - sbuf2[i * 2 + 0] = fetch(s2, x1); - sbuf2[i * 2 + 1] = fetch(s2, x2); - } + fetcher(sbuf1, sbuf2, len, data->texture, fx, fy, fdx, fdy); - fx += fdx; - fy += fdy; - } layout->convertToARGB64PM((QRgba64 *)buf1, sbuf1, len * 2, clut, 0); layout->convertToARGB64PM((QRgba64 *)buf2, sbuf2, len * 2, clut, 0); for (int i = 0; i < len; ++i) { - int distx = (fracX & 0x0000ffff); - int disty = (fracY & 0x0000ffff); + int distx = (fx & 0x0000ffff); + int disty = (fy & 0x0000ffff); b[i] = interpolate_4_pixels_rgb64((QRgba64 *)buf1 + i*2, (QRgba64 *)buf2 + i*2, distx, disty); - fracX += fdx; - fracY += fdy; + fx += fdx; + fy += fdy; } length -= len; @@ -3238,6 +3165,12 @@ static const QRgba64 *QT_FASTCALL fetchTransformedBilinear64(QRgba64 *buffer, co } } } else { + const QTextureData &image = data->texture; + + const qreal fdx = data->m11; + const qreal fdy = data->m12; + const qreal fdw = data->m13; + qreal fx = data->m21 * cy + data->m11 * cx + data->dx; qreal fy = data->m22 * cy + data->m12 * cx + data->dy; qreal fw = data->m23 * cy + data->m13 * cx + data->m33; @@ -3267,8 +3200,8 @@ static const QRgba64 *QT_FASTCALL fetchTransformedBilinear64(QRgba64 *buffer, co distxs[i] = int((px - x1) * (1<<16)); distys[i] = int((py - y1) * (1<<16)); - fetchTransformedBilinear_pixelBounds<blendType>(image_width, image_x1, image_x2, x1, x2); - fetchTransformedBilinear_pixelBounds<blendType>(image_height, image_y1, image_y2, y1, y2); + fetchTransformedBilinear_pixelBounds<blendType>(image.width, image.x1, image.x2 - 1, x1, x2); + fetchTransformedBilinear_pixelBounds<blendType>(image.height, image.y1, image.y2 - 1, y1, y2); const uchar *s1 = data->texture.scanLine(y1); const uchar *s2 = data->texture.scanLine(y2); diff --git a/src/gui/painting/qemulationpaintengine.cpp b/src/gui/painting/qemulationpaintengine.cpp index 586b71557e..e6686e3721 100644 --- a/src/gui/painting/qemulationpaintengine.cpp +++ b/src/gui/painting/qemulationpaintengine.cpp @@ -72,6 +72,14 @@ QPainterState *QEmulationPaintEngine::createState(QPainterState *orig) const return real_engine->createState(orig); } +static inline void combineXForm(QBrush *brush, const QRectF &r) +{ + QTransform t = brush->transform(); + t.translate(r.x(), r.y()); + t.scale(r.width(), r.height()); + brush->setTransform(t); +} + void QEmulationPaintEngine::fill(const QVectorPath &path, const QBrush &brush) { QPainterState *s = state(); @@ -84,26 +92,14 @@ void QEmulationPaintEngine::fill(const QVectorPath &path, const QBrush &brush) Qt::BrushStyle style = qbrush_style(brush); if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) { - const QGradient *g = brush.gradient(); - - if (g->coordinateMode() > QGradient::LogicalMode) { - if (g->coordinateMode() == QGradient::StretchToDeviceMode) { - QBrush copy = brush; - QTransform mat = copy.transform(); - mat.scale(real_engine->painter()->device()->width(), real_engine->painter()->device()->height()); - copy.setTransform(mat); - real_engine->fill(path, copy); - return; - } else if (g->coordinateMode() == QGradient::ObjectBoundingMode) { - QBrush copy = brush; - QTransform mat = copy.transform(); - QRectF r = path.controlPointRect(); - mat.translate(r.x(), r.y()); - mat.scale(r.width(), r.height()); - copy.setTransform(mat); - real_engine->fill(path, copy); - return; - } + QGradient::CoordinateMode coMode = brush.gradient()->coordinateMode(); + if (coMode > QGradient::LogicalMode) { + QBrush copy = brush; + const QPaintDevice *d = real_engine->painter()->device(); + QRectF r = (coMode == QGradient::ObjectBoundingMode) ? path.controlPointRect() : QRectF(0, 0, d->width(), d->height()); + combineXForm(©, r); + real_engine->fill(path, copy); + return; } } @@ -124,27 +120,15 @@ void QEmulationPaintEngine::stroke(const QVectorPath &path, const QPen &pen) QBrush brush = pen.brush(); QPen copy = pen; Qt::BrushStyle style = qbrush_style(brush); - if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern) { - const QGradient *g = brush.gradient(); - - if (g->coordinateMode() > QGradient::LogicalMode) { - if (g->coordinateMode() == QGradient::StretchToDeviceMode) { - QTransform mat = brush.transform(); - mat.scale(real_engine->painter()->device()->width(), real_engine->painter()->device()->height()); - brush.setTransform(mat); - copy.setBrush(brush); - real_engine->stroke(path, copy); - return; - } else if (g->coordinateMode() == QGradient::ObjectBoundingMode) { - QTransform mat = brush.transform(); - QRectF r = path.controlPointRect(); - mat.translate(r.x(), r.y()); - mat.scale(r.width(), r.height()); - brush.setTransform(mat); - copy.setBrush(brush); - real_engine->stroke(path, copy); - return; - } + if (style >= Qt::LinearGradientPattern && style <= Qt::ConicalGradientPattern ) { + QGradient::CoordinateMode coMode = brush.gradient()->coordinateMode(); + if (coMode > QGradient::LogicalMode) { + const QPaintDevice *d = real_engine->painter()->device(); + QRectF r = (coMode == QGradient::ObjectBoundingMode) ? path.controlPointRect() : QRectF(0, 0, d->width(), d->height()); + combineXForm(&brush, r); + copy.setBrush(brush); + real_engine->stroke(path, copy); + return; } } @@ -179,18 +163,16 @@ void QEmulationPaintEngine::drawTextItem(const QPointF &p, const QTextItem &text QGradient g = *s->pen.brush().gradient(); if (g.coordinateMode() > QGradient::LogicalMode) { - QTransform mat = s->pen.brush().transform(); - if (g.coordinateMode() == QGradient::StretchToDeviceMode) { - mat.scale(real_engine->painter()->device()->width(), real_engine->painter()->device()->height()); - } else if (g.coordinateMode() == QGradient::ObjectBoundingMode) { - const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); - QRectF r(p.x(), p.y() - ti.ascent.toReal(), ti.width.toReal(), (ti.ascent + ti.descent + 1).toReal()); - mat.translate(r.x(), r.y()); - mat.scale(r.width(), r.height()); - } + QBrush copy = s->pen.brush(); + const QPaintDevice *d = real_engine->painter()->device(); + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + QRectF r = (g.coordinateMode() == QGradient::ObjectBoundingMode) ? + QRectF(p.x(), p.y() - ti.ascent.toReal(), ti.width.toReal(), (ti.ascent + ti.descent + 1).toReal()) : + QRectF(0, 0, d->width(), d->height()); + combineXForm(©, r); g.setCoordinateMode(QGradient::LogicalMode); QBrush brush(g); - brush.setTransform(mat); + brush.setTransform(copy.transform()); s->pen.setBrush(brush); penChanged(); real_engine->drawTextItem(p, textItem); diff --git a/src/gui/painting/qgrayraster.c b/src/gui/painting/qgrayraster.c index 4a135e25d8..0143e9b602 100644 --- a/src/gui/painting/qgrayraster.c +++ b/src/gui/painting/qgrayraster.c @@ -43,7 +43,7 @@ /* */ /* A new `perfect' anti-aliasing renderer (body). */ /* */ -/* Copyright 2000-2001, 2002, 2003 by */ +/* Copyright 2000-2016 by */ /* David Turner, Robert Wilhelm, and Werner Lemberg. */ /* */ /* This file is part of the FreeType project, and may only be used, */ @@ -119,10 +119,6 @@ /* */ /*************************************************************************/ -/* experimental support for gamma correction within the rasterizer */ -#define xxxGRAYS_USE_GAMMA - - /*************************************************************************/ /* */ /* The macro QT_FT_COMPONENT is used in trace mode. It is an implicit */ @@ -133,6 +129,28 @@ #define QT_FT_COMPONENT trace_smooth +/* Auxiliary macros for token concatenation. */ +#define QT_FT_ERR_XCAT( x, y ) x ## y +#define QT_FT_ERR_CAT( x, y ) QT_FT_ERR_XCAT( x, y ) + +#define QT_FT_BEGIN_STMNT do { +#define QT_FT_END_STMNT } while ( 0 ) + +#define QT_FT_MAX( a, b ) ( (a) > (b) ? (a) : (b) ) +#define QT_FT_ABS( a ) ( (a) < 0 ? -(a) : (a) ) + + +/* + * Approximate sqrt(x*x+y*y) using the `alpha max plus beta min' + * algorithm. We use alpha = 1, beta = 3/8, giving us results with a + * largest error less than 7% compared to the exact value. + */ +#define QT_FT_HYPOT( x, y ) \ + ( x = QT_FT_ABS( x ), \ + y = QT_FT_ABS( y ), \ + x > y ? x + ( 3 * y >> 3 ) \ + : y + ( 3 * x >> 3 ) ) + #define ErrRaster_MemoryOverflow -4 #if defined(VXWORKS) @@ -150,6 +168,9 @@ #define qt_ft_longjmp longjmp #define qt_ft_jmp_buf jmp_buf +#include <stddef.h> +typedef ptrdiff_t QT_FT_PtrDist; + #define ErrRaster_Invalid_Mode -2 #define ErrRaster_Invalid_Outline -1 #define ErrRaster_Invalid_Argument -3 @@ -169,15 +190,10 @@ #define QT_FT_UNUSED( x ) (void) x - /* Disable the tracing mechanism for simplicity -- developers can */ - /* activate it easily by redefining these two macros. */ -#ifndef QT_FT_ERROR -#define QT_FT_ERROR( x ) do ; while ( 0 ) /* nothing */ -#endif - -#ifndef QT_FT_TRACE -#define QT_FT_TRACE( x ) do ; while ( 0 ) /* nothing */ -#endif +#define QT_FT_TRACE5( x ) do { } while ( 0 ) /* nothing */ +#define QT_FT_TRACE7( x ) do { } while ( 0 ) /* nothing */ +#define QT_FT_ERROR( x ) do { } while ( 0 ) /* nothing */ +#define QT_FT_THROW( e ) QT_FT_ERR_CAT( ErrRaster_, e ) #ifndef QT_FT_MEM_SET #define QT_FT_MEM_SET( d, s, c ) qt_ft_memset( d, s, c ) @@ -187,9 +203,6 @@ #define QT_FT_MEM_ZERO( dest, count ) QT_FT_MEM_SET( dest, 0, count ) #endif - /* define this to dump debugging information */ -#define xxxDEBUG_GRAYS - #define RAS_ARG PWorker worker #define RAS_ARG_ PWorker worker, @@ -199,26 +212,47 @@ #define ras (*worker) - /* must be at least 6 bits! */ #define PIXEL_BITS 8 #define ONE_PIXEL ( 1L << PIXEL_BITS ) -#define PIXEL_MASK ( -1L << PIXEL_BITS ) #define TRUNC( x ) ( (TCoord)( (x) >> PIXEL_BITS ) ) -#define SUBPIXELS( x ) ( (TPos)(x) * ( 1 << PIXEL_BITS ) ) +#define SUBPIXELS( x ) ( (TPos)(x) * ONE_PIXEL ) #define FLOOR( x ) ( (x) & -ONE_PIXEL ) #define CEILING( x ) ( ( (x) + ONE_PIXEL - 1 ) & -ONE_PIXEL ) #define ROUND( x ) ( ( (x) + ONE_PIXEL / 2 ) & -ONE_PIXEL ) #if PIXEL_BITS >= 6 -#define UPSCALE( x ) ( (x) * ( 1 << ( PIXEL_BITS - 6 ) ) ) +#define UPSCALE( x ) ( (x) * ( ONE_PIXEL >> 6 ) ) #define DOWNSCALE( x ) ( (x) >> ( PIXEL_BITS - 6 ) ) #else #define UPSCALE( x ) ( (x) >> ( 6 - PIXEL_BITS ) ) -#define DOWNSCALE( x ) ( (x) << ( 6 - PIXEL_BITS ) ) +#define DOWNSCALE( x ) ( (x) * ( 64 >> PIXEL_BITS ) ) #endif +/* Compute `dividend / divisor' and return both its quotient and */ +/* remainder, cast to a specific type. This macro also ensures that */ +/* the remainder is always positive. */ +#define QT_FT_DIV_MOD( type, dividend, divisor, quotient, remainder ) \ +QT_FT_BEGIN_STMNT \ + (quotient) = (type)( (dividend) / (divisor) ); \ + (remainder) = (type)( (dividend) % (divisor) ); \ + if ( (remainder) < 0 ) \ + { \ + (quotient)--; \ + (remainder) += (type)(divisor); \ + } \ +QT_FT_END_STMNT + + /* These macros speed up repetitive divisions by replacing them */ + /* with multiplications and right shifts. */ +#define QT_FT_UDIVPREP( b ) \ + long b ## _r = (long)( ULONG_MAX >> PIXEL_BITS ) / ( b ) +#define QT_FT_UDIV( a, b ) \ + ( ( (unsigned long)( a ) * (unsigned long)( b ## _r ) ) >> \ + ( sizeof( long ) * CHAR_BIT - PIXEL_BITS ) ) + + /*************************************************************************/ /* */ /* TYPE DEFINITIONS */ @@ -228,28 +262,9 @@ /* need to define them to "float" or "double" when experimenting with */ /* new algorithms */ - typedef int TCoord; /* integer scanline/pixel coordinate */ - typedef int TPos; /* sub-pixel coordinate */ - - /* determine the type used to store cell areas. This normally takes at */ - /* least PIXEL_BITS*2 + 1 bits. On 16-bit systems, we need to use */ - /* `long' instead of `int', otherwise bad things happen */ - -#if PIXEL_BITS <= 7 - - typedef int TArea; - -#else /* PIXEL_BITS >= 8 */ - - /* approximately determine the size of integers using an ANSI-C header */ -#if QT_FT_UINT_MAX == 0xFFFFU - typedef long TArea; -#else - typedef int TArea; -#endif - -#endif /* PIXEL_BITS >= 8 */ - + typedef long TCoord; /* integer scanline/pixel coordinate */ + typedef long TPos; /* sub-pixel coordinate */ + typedef long TArea ; /* cell areas, coordinate products */ /* maximal number of gray spans in a call to the span callback */ #define QT_FT_MAX_GRAY_SPANS 256 @@ -279,17 +294,11 @@ int invalid; PCell cells; - int max_cells; - int num_cells; + QT_FT_PtrDist max_cells; + QT_FT_PtrDist num_cells; - TCoord cx, cy; TPos x, y; - TPos last_ey; - - QT_FT_Vector bez_stack[32 * 3 + 1]; - int lev_stack[32]; - QT_FT_Outline outline; QT_FT_Bitmap target; QT_FT_BBox clip_box; @@ -302,8 +311,6 @@ int band_size; int band_shoot; - int conic_level; - int cubic_level; qt_ft_jmp_buf jump_buffer; @@ -311,7 +318,7 @@ long buffer_size; PCell* ycells; - int ycount; + TPos ycount; int skip_spans; } TWorker, *PWorker; @@ -341,7 +348,7 @@ /* */ static void gray_init_cells( RAS_ARG_ void* buffer, - long byte_size ) + long byte_size ) { ras.buffer = buffer; ras.buffer_size = byte_size; @@ -404,31 +411,25 @@ /* */ /* Record the current cell in the table. */ /* */ - static void - gray_record_cell( RAS_ARG ) + static PCell + gray_find_cell( RAS_ARG ) { PCell *pcell, cell; - int x = ras.ex; + TPos x = ras.ex; - if ( ras.invalid || !( ras.area | ras.cover ) ) - return; - if ( x > ras.max_ex ) - x = ras.max_ex; + if ( x > ras.count_ex ) + x = ras.count_ex; pcell = &ras.ycells[ras.ey]; - for (;;) { cell = *pcell; if ( cell == NULL || cell->x > x ) break; - if ( cell->x == x ) { - cell->area += ras.area; - cell->cover += ras.cover; - return; - } + if ( cell->x == x ) + goto Exit; pcell = &cell->next; } @@ -438,11 +439,28 @@ cell = ras.cells + ras.num_cells++; cell->x = x; - cell->area = ras.area; - cell->cover = ras.cover; + cell->area = 0; + cell->cover = 0; cell->next = *pcell; *pcell = cell; + + Exit: + return cell; + } + + + static void + gray_record_cell( RAS_ARG ) + { + if ( ras.area | ras.cover ) + { + PCell cell = gray_find_cell( RAS_VAR ); + + + cell->area += ras.area; + cell->cover += ras.cover; + } } @@ -488,8 +506,8 @@ ras.ey = ey; } - ras.invalid = ( (unsigned)ey >= (unsigned)ras.count_ey || - ex >= ras.count_ex ); + ras.invalid = ( (unsigned int)ey >= (unsigned int)ras.count_ey || + ex >= ras.count_ex ); } @@ -511,12 +529,13 @@ ras.cover = 0; ras.ex = ex - ras.min_ex; ras.ey = ey - ras.min_ey; - ras.last_ey = SUBPIXELS( ey ); ras.invalid = 0; gray_set_cell( RAS_VAR_ ex, ey ); } +// The new render-line implementation is not yet used +#if 1 /*************************************************************************/ /* */ @@ -529,9 +548,9 @@ TPos x2, TCoord y2 ) { - TCoord ex1, ex2, fx1, fx2, delta; + TCoord ex1, ex2, fx1, fx2, delta, mod; int p, first, dx; - int incr, lift, mod, rem; + int incr; dx = x2 - x1; @@ -573,13 +592,7 @@ dx = -dx; } - delta = (TCoord)( p / dx ); - mod = (TCoord)( p % dx ); - if ( mod < 0 ) - { - delta--; - mod += (TCoord)dx; - } + QT_FT_DIV_MOD( TCoord, p, dx, delta, mod ); ras.area += (TArea)( fx1 + first ) * delta; ras.cover += delta; @@ -590,14 +603,11 @@ if ( ex1 != ex2 ) { - p = ONE_PIXEL * ( y2 - y1 + delta ); - lift = (TCoord)( p / dx ); - rem = (TCoord)( p % dx ); - if ( rem < 0 ) - { - lift--; - rem += (TCoord)dx; - } + TCoord lift, rem; + + + p = ONE_PIXEL * ( y2 - y1 + delta ); + QT_FT_DIV_MOD( TCoord, p, dx, lift, rem ); mod -= (int)dx; @@ -633,38 +643,24 @@ gray_render_line( RAS_ARG_ TPos to_x, TPos to_y ) { - TCoord ey1, ey2, fy1, fy2; + TCoord ey1, ey2, fy1, fy2, mod; TPos dx, dy, x, x2; int p, first; - int delta, rem, mod, lift, incr; + int delta, rem, lift, incr; - ey1 = TRUNC( ras.last_ey ); + ey1 = TRUNC( ras.y ); ey2 = TRUNC( to_y ); /* if (ey2 >= ras.max_ey) ey2 = ras.max_ey-1; */ - fy1 = (TCoord)( ras.y - ras.last_ey ); + fy1 = (TCoord)( ras.y - SUBPIXELS( ey1 ) ); fy2 = (TCoord)( to_y - SUBPIXELS( ey2 ) ); dx = to_x - ras.x; dy = to_y - ras.y; - /* XXX: we should do something about the trivial case where dx == 0, */ - /* as it happens very often! */ - /* perform vertical clipping */ - { - TCoord min, max; - - - min = ey1; - max = ey2; - if ( ey1 > ey2 ) - { - min = ey2; - max = ey1; - } - if ( min >= ras.max_ey || max < ras.min_ey ) - goto End; - } + if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || + ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) + goto End; /* everything is on a single scanline */ if ( ey1 == ey2 ) @@ -771,13 +767,7 @@ if ( ey1 != ey2 ) { p = ONE_PIXEL * dx; - lift = (int)( p / dy ); - rem = (int)( p % dy ); - if ( rem < 0 ) - { - lift--; - rem += (int)dy; - } + QT_FT_DIV_MOD( int, p, dy, lift, rem ); mod -= (int)dy; while ( ey1 != ey2 ) @@ -808,10 +798,147 @@ End: ras.x = to_x; ras.y = to_y; - ras.last_ey = SUBPIXELS( ey2 ); } +#else + + /*************************************************************************/ + /* */ + /* Render a straight line across multiple cells in any direction. */ + /* */ + static void + gray_render_line( RAS_ARG_ TPos to_x, + TPos to_y ) + { + TPos dx, dy, fx1, fy1, fx2, fy2; + TCoord ex1, ex2, ey1, ey2; + + + ex1 = TRUNC( ras.x ); + ex2 = TRUNC( to_x ); + ey1 = TRUNC( ras.y ); + ey2 = TRUNC( to_y ); + + /* perform vertical clipping */ + if ( ( ey1 >= ras.max_ey && ey2 >= ras.max_ey ) || + ( ey1 < ras.min_ey && ey2 < ras.min_ey ) ) + goto End; + + dx = to_x - ras.x; + dy = to_y - ras.y; + + fx1 = ras.x - SUBPIXELS( ex1 ); + fy1 = ras.y - SUBPIXELS( ey1 ); + + if ( ex1 == ex2 && ey1 == ey2 ) /* inside one cell */ + ; + else if ( dy == 0 ) /* ex1 != ex2 */ /* any horizontal line */ + { + ex1 = ex2; + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } + else if ( dx == 0 ) + { + if ( dy > 0 ) /* vertical line up */ + do + { + fy2 = ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * fx1 * 2; + fy1 = 0; + ey1++; + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } while ( ey1 != ey2 ); + else /* vertical line down */ + do + { + fy2 = 0; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * fx1 * 2; + fy1 = ONE_PIXEL; + ey1--; + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } while ( ey1 != ey2 ); + } + else /* any other line */ + { + TArea prod = dx * fy1 - dy * fx1; + QT_FT_UDIVPREP( dx ); + QT_FT_UDIVPREP( dy ); + + + /* The fundamental value `prod' determines which side and the */ + /* exact coordinate where the line exits current cell. It is */ + /* also easily updated when moving from one cell to the next. */ + do + { + if ( prod <= 0 && + prod - dx * ONE_PIXEL > 0 ) /* left */ + { + fx2 = 0; + fy2 = (TPos)QT_FT_UDIV( -prod, -dx ); + prod -= dy * ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = ONE_PIXEL; + fy1 = fy2; + ex1--; + } + else if ( prod - dx * ONE_PIXEL <= 0 && + prod - dx * ONE_PIXEL + dy * ONE_PIXEL > 0 ) /* up */ + { + prod -= dx * ONE_PIXEL; + fx2 = (TPos)QT_FT_UDIV( -prod, dy ); + fy2 = ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = fx2; + fy1 = 0; + ey1++; + } + else if ( prod - dx * ONE_PIXEL + dy * ONE_PIXEL <= 0 && + prod + dy * ONE_PIXEL >= 0 ) /* right */ + { + prod += dy * ONE_PIXEL; + fx2 = ONE_PIXEL; + fy2 = (TPos)QT_FT_UDIV( prod, dx ); + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = 0; + fy1 = fy2; + ex1++; + } + else /* ( prod + dy * ONE_PIXEL < 0 && + prod > 0 ) down */ + { + fx2 = (TPos)QT_FT_UDIV( prod, -dy ); + fy2 = 0; + prod += dx * ONE_PIXEL; + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + fx1 = fx2; + fy1 = ONE_PIXEL; + ey1--; + } + + gray_set_cell( RAS_VAR_ ex1, ey1 ); + } while ( ex1 != ex2 || ey1 != ey2 ); + } + + fx2 = to_x - SUBPIXELS( ex2 ); + fy2 = to_y - SUBPIXELS( ey2 ); + + ras.cover += ( fy2 - fy1 ); + ras.area += ( fy2 - fy1 ) * ( fx1 + fx2 ); + + End: + ras.x = to_x; + ras.y = to_y; + } + +#endif + static void gray_split_conic( QT_FT_Vector* base ) { @@ -836,52 +963,11 @@ gray_render_conic( RAS_ARG_ const QT_FT_Vector* control, const QT_FT_Vector* to ) { + QT_FT_Vector bez_stack[16 * 2 + 1]; /* enough to accommodate bisections */ + QT_FT_Vector* arc = bez_stack; TPos dx, dy; - int top, level; - int* levels; - QT_FT_Vector* arc; - - - dx = DOWNSCALE( ras.x ) + to->x - ( control->x << 1 ); - if ( dx < 0 ) - dx = -dx; - dy = DOWNSCALE( ras.y ) + to->y - ( control->y << 1 ); - if ( dy < 0 ) - dy = -dy; - if ( dx < dy ) - dx = dy; - - level = 1; - dx = dx / ras.conic_level; - while ( dx > 0 ) - { - dx >>= 2; - level++; - } + int draw, split; - /* a shortcut to speed things up */ - if ( level <= 1 ) - { - /* we compute the mid-point directly in order to avoid */ - /* calling gray_split_conic() */ - TPos to_x, to_y, mid_x, mid_y; - - - to_x = UPSCALE( to->x ); - to_y = UPSCALE( to->y ); - mid_x = ( ras.x + to_x + 2 * UPSCALE( control->x ) ) / 4; - mid_y = ( ras.y + to_y + 2 * UPSCALE( control->y ) ) / 4; - - gray_render_line( RAS_VAR_ mid_x, mid_y ); - gray_render_line( RAS_VAR_ to_x, to_y ); - - return; - } - - arc = ras.bez_stack; - levels = ras.lev_stack; - top = 0; - levels[0] = level; arc[0].x = UPSCALE( to->x ); arc[0].y = UPSCALE( to->y ); @@ -890,54 +976,51 @@ arc[2].x = ras.x; arc[2].y = ras.y; - while ( top >= 0 ) + /* short-cut the arc that crosses the current band */ + if ( ( TRUNC( arc[0].y ) >= ras.max_ey && + TRUNC( arc[1].y ) >= ras.max_ey && + TRUNC( arc[2].y ) >= ras.max_ey ) || + ( TRUNC( arc[0].y ) < ras.min_ey && + TRUNC( arc[1].y ) < ras.min_ey && + TRUNC( arc[2].y ) < ras.min_ey ) ) { - level = levels[top]; - if ( level > 1 ) - { - /* check that the arc crosses the current band */ - TPos min, max, y; - - - min = max = arc[0].y; - - y = arc[1].y; - if ( y < min ) min = y; - if ( y > max ) max = y; + ras.x = arc[0].x; + ras.y = arc[0].y; + return; + } - y = arc[2].y; - if ( y < min ) min = y; - if ( y > max ) max = y; + dx = QT_FT_ABS( arc[2].x + arc[0].x - 2 * arc[1].x ); + dy = QT_FT_ABS( arc[2].y + arc[0].y - 2 * arc[1].y ); + if ( dx < dy ) + dx = dy; - if ( TRUNC( min ) >= ras.max_ey || TRUNC( max ) < ras.min_ey ) - goto Draw; + /* We can calculate the number of necessary bisections because */ + /* each bisection predictably reduces deviation exactly 4-fold. */ + /* Even 32-bit deviation would vanish after 16 bisections. */ + draw = 1; + while ( dx > ONE_PIXEL / 4 ) + { + dx >>= 2; + draw <<= 1; + } + /* We use decrement counter to count the total number of segments */ + /* to draw starting from 2^level. Before each draw we split as */ + /* many times as there are trailing zeros in the counter. */ + do + { + split = 1; + while ( ( draw & split ) == 0 ) + { gray_split_conic( arc ); arc += 2; - top++; - levels[top] = levels[top - 1] = level - 1; - continue; + split <<= 1; } - Draw: - { - TPos to_x, to_y, mid_x, mid_y; - - - to_x = arc[0].x; - to_y = arc[0].y; - mid_x = ( ras.x + to_x + 2 * arc[1].x ) / 4; - mid_y = ( ras.y + to_y + 2 * arc[1].y ) / 4; - - gray_render_line( RAS_VAR_ mid_x, mid_y ); - gray_render_line( RAS_VAR_ to_x, to_y ); - - top--; - arc -= 2; - } - } + gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + arc -= 2; - return; + } while ( --draw ); } @@ -974,60 +1057,13 @@ const QT_FT_Vector* control2, const QT_FT_Vector* to ) { - TPos dx, dy, da, db; - int top, level; - int* levels; - QT_FT_Vector* arc; + QT_FT_Vector bez_stack[16 * 3 + 1]; /* enough to accommodate bisections */ + QT_FT_Vector* arc = bez_stack; + TPos dx, dy, dx_, dy_; + TPos dx1, dy1, dx2, dy2; + TPos L, s, s_limit; - dx = DOWNSCALE( ras.x ) + to->x - ( control1->x << 1 ); - if ( dx < 0 ) - dx = -dx; - dy = DOWNSCALE( ras.y ) + to->y - ( control1->y << 1 ); - if ( dy < 0 ) - dy = -dy; - if ( dx < dy ) - dx = dy; - da = dx; - - dx = DOWNSCALE( ras.x ) + to->x - 3 * ( control1->x + control2->x ); - if ( dx < 0 ) - dx = -dx; - dy = DOWNSCALE( ras.y ) + to->y - 3 * ( control1->y + control2->y ); - if ( dy < 0 ) - dy = -dy; - if ( dx < dy ) - dx = dy; - db = dx; - - level = 1; - da = da / ras.cubic_level; - db = db / ras.conic_level; - while ( da > 0 || db > 0 ) - { - da >>= 2; - db >>= 3; - level++; - } - - if ( level <= 1 ) - { - TPos to_x, to_y, mid_x, mid_y; - - - to_x = UPSCALE( to->x ); - to_y = UPSCALE( to->y ); - mid_x = ( ras.x + to_x + - 3 * UPSCALE( control1->x + control2->x ) ) / 8; - mid_y = ( ras.y + to_y + - 3 * UPSCALE( control1->y + control2->y ) ) / 8; - - gray_render_line( RAS_VAR_ mid_x, mid_y ); - gray_render_line( RAS_VAR_ to_x, to_y ); - return; - } - - arc = ras.bez_stack; arc[0].x = UPSCALE( to->x ); arc[0].y = UPSCALE( to->y ); arc[1].x = UPSCALE( control2->x ); @@ -1037,56 +1073,77 @@ arc[3].x = ras.x; arc[3].y = ras.y; - levels = ras.lev_stack; - top = 0; - levels[0] = level; + /* short-cut the arc that crosses the current band */ + if ( ( TRUNC( arc[0].y ) >= ras.max_ey && + TRUNC( arc[1].y ) >= ras.max_ey && + TRUNC( arc[2].y ) >= ras.max_ey && + TRUNC( arc[3].y ) >= ras.max_ey ) || + ( TRUNC( arc[0].y ) < ras.min_ey && + TRUNC( arc[1].y ) < ras.min_ey && + TRUNC( arc[2].y ) < ras.min_ey && + TRUNC( arc[3].y ) < ras.min_ey ) ) + { + ras.x = arc[0].x; + ras.y = arc[0].y; + return; + } - while ( top >= 0 ) + for (;;) { - level = levels[top]; - if ( level > 1 ) - { - /* check that the arc crosses the current band */ - TPos min, max, y; - - - min = max = arc[0].y; - y = arc[1].y; - if ( y < min ) min = y; - if ( y > max ) max = y; - y = arc[2].y; - if ( y < min ) min = y; - if ( y > max ) max = y; - y = arc[3].y; - if ( y < min ) min = y; - if ( y > max ) max = y; - if ( TRUNC( min ) >= ras.max_ey || TRUNC( max ) < 0 ) - goto Draw; - gray_split_cubic( arc ); - arc += 3; - top ++; - levels[top] = levels[top - 1] = level - 1; - continue; - } + /* Decide whether to split or draw. See `Rapid Termination */ + /* Evaluation for Recursive Subdivision of Bezier Curves' by Thomas */ + /* F. Hain, at */ + /* http://www.cis.southalabama.edu/~hain/general/Publications/Bezier/Camera-ready%20CISST02%202.pdf */ - Draw: - { - TPos to_x, to_y, mid_x, mid_y; + /* dx and dy are x and y components of the P0-P3 chord vector. */ + dx = dx_ = arc[3].x - arc[0].x; + dy = dy_ = arc[3].y - arc[0].y; - to_x = arc[0].x; - to_y = arc[0].y; - mid_x = ( ras.x + to_x + 3 * ( arc[1].x + arc[2].x ) ) / 8; - mid_y = ( ras.y + to_y + 3 * ( arc[1].y + arc[2].y ) ) / 8; + L = QT_FT_HYPOT( dx_, dy_ ); - gray_render_line( RAS_VAR_ mid_x, mid_y ); - gray_render_line( RAS_VAR_ to_x, to_y ); - top --; - arc -= 3; - } - } + /* Avoid possible arithmetic overflow below by splitting. */ + if ( L > 32767 ) + goto Split; + + /* Max deviation may be as much as (s/L) * 3/4 (if Hain's v = 1). */ + s_limit = L * (TPos)( ONE_PIXEL / 6 ); + + /* s is L * the perpendicular distance from P1 to the line P0-P3. */ + dx1 = arc[1].x - arc[0].x; + dy1 = arc[1].y - arc[0].y; + s = QT_FT_ABS( dy * dx1 - dx * dy1 ); + + if ( s > s_limit ) + goto Split; + + /* s is L * the perpendicular distance from P2 to the line P0-P3. */ + dx2 = arc[2].x - arc[0].x; + dy2 = arc[2].y - arc[0].y; + s = QT_FT_ABS( dy * dx2 - dx * dy2 ); + + if ( s > s_limit ) + goto Split; + + /* Split super curvy segments where the off points are so far + from the chord that the angles P0-P1-P3 or P0-P2-P3 become + acute as detected by appropriate dot products. */ + if ( dx1 * ( dx1 - dx ) + dy1 * ( dy1 - dy ) > 0 || + dx2 * ( dx2 - dx ) + dy2 * ( dy2 - dy ) > 0 ) + goto Split; - return; + gray_render_line( RAS_VAR_ arc[0].x, arc[0].y ); + + if ( arc == bez_stack ) + return; + + arc -= 3; + continue; + + Split: + gray_split_cubic( arc ); + arc += 3; + } } @@ -1099,7 +1156,8 @@ /* record current cell, if any */ - gray_record_cell( worker ); + if ( !ras.invalid ) + gray_record_cell( worker ); /* start to a new position */ x = UPSCALE( to->x ); @@ -1107,15 +1165,15 @@ gray_start_cell( worker, TRUNC( x ), TRUNC( y ) ); - worker->x = x; - worker->y = y; + ras.x = x; + ras.y = y; return 0; } static void - gray_render_span( int count, + gray_render_span( int count, const QT_FT_Span* spans, - PWorker worker ) + PWorker worker ) { unsigned char* p; QT_FT_Bitmap* map = &worker->target; @@ -1127,34 +1185,30 @@ /* first of all, compute the scanline offset */ p = (unsigned char*)map->buffer - spans->y * map->pitch; if ( map->pitch >= 0 ) - p += ( map->rows - 1 ) * map->pitch; + p += ( map->rows - 1 ) * (unsigned int)map->pitch; if ( coverage ) { + unsigned char* q = p + spans->x; + + /* For small-spans it is faster to do it by ourselves than * calling `memset'. This is mainly due to the cost of the * function call. */ - if ( spans->len >= 8 ) - QT_FT_MEM_SET( p + spans->x, (unsigned char)coverage, spans->len ); - else + switch ( spans->len ) { - unsigned char* q = p + spans->x; - - - switch ( spans->len ) - { - case 7: *q++ = (unsigned char)coverage; Q_FALLTHROUGH(); - case 6: *q++ = (unsigned char)coverage; Q_FALLTHROUGH(); - case 5: *q++ = (unsigned char)coverage; Q_FALLTHROUGH(); - case 4: *q++ = (unsigned char)coverage; Q_FALLTHROUGH(); - case 3: *q++ = (unsigned char)coverage; Q_FALLTHROUGH(); - case 2: *q++ = (unsigned char)coverage; Q_FALLTHROUGH(); - case 1: *q = (unsigned char)coverage; - default: - ; - } + case 7: *q++ = coverage; Q_FALLTHROUGH(); + case 6: *q++ = coverage; Q_FALLTHROUGH(); + case 5: *q++ = coverage; Q_FALLTHROUGH(); + case 4: *q++ = coverage; Q_FALLTHROUGH(); + case 3: *q++ = coverage; Q_FALLTHROUGH(); + case 2: *q++ = coverage; Q_FALLTHROUGH(); + case 1: *q = coverage; Q_FALLTHROUGH(); + case 0: break; + default: + QT_FT_MEM_SET( q, coverage, spans->len ); } } } @@ -1167,9 +1221,7 @@ TPos area, int acount ) { - QT_FT_Span* span; - int coverage; - int skip; + int coverage; /* compute the coverage line's coverage, depending on the */ @@ -1202,14 +1254,24 @@ x += (TCoord)ras.min_ex; /* QT_FT_Span.x is a 16-bit short, so limit our coordinates appropriately */ - if ( x >= 32768 ) + if ( x >= 32767 ) x = 32767; + /* QT_FT_Span.y is a 16-bit short, so limit our coordinates appropriately */ + if ( y >= 32767 ) + y = 32767; + if ( coverage ) { + QT_FT_Span* span; + int count; + int skip; + + /* see whether we can add this span to the current list */ - span = ras.gray_spans + ras.num_gray_spans - 1; - if ( ras.num_gray_spans > 0 && + count = ras.num_gray_spans; + span = ras.gray_spans + count - 1; + if ( count > 0 && span->y == y && (int)span->x + span->len == (int)x && span->coverage == coverage ) @@ -1218,9 +1280,9 @@ return; } - if ( ras.num_gray_spans >= QT_FT_MAX_GRAY_SPANS ) + if ( count >= QT_FT_MAX_GRAY_SPANS ) { - if ( ras.render_span && ras.num_gray_spans > ras.skip_spans ) + if ( ras.render_span && count > ras.skip_spans ) { skip = ras.skip_spans > 0 ? ras.skip_spans : 0; ras.render_span( ras.num_gray_spans - skip, @@ -1302,6 +1364,8 @@ if ( ras.num_cells == 0 ) return; + QT_FT_TRACE7(( "gray_sweep: start\n" )); + for ( yindex = 0; yindex < ras.ycount; yindex++ ) { PCell cell = ras.ycells[yindex]; @@ -1331,6 +1395,8 @@ gray_hline( RAS_VAR_ x, yindex, cover * ( ONE_PIXEL * 2 ), ras.count_ex - x ); } + + QT_FT_TRACE7(( "gray_sweep: end\n" )); } /*************************************************************************/ @@ -1364,7 +1430,7 @@ /* */ static int QT_FT_Outline_Decompose( const QT_FT_Outline* outline, - void* user ) + void* user ) { #undef SCALED #define SCALED( x ) (x) @@ -1382,6 +1448,9 @@ int error; char tag; /* current point's state */ + if ( !outline ) + return ErrRaster_Invalid_Outline; + first = 0; for ( n = 0; n < outline->n_contours; n++ ) @@ -1390,16 +1459,17 @@ last = outline->contours[n]; + if ( last < 0 ) + goto Invalid_Outline; limit = outline->points + last; - v_start = outline->points[first]; - v_last = outline->points[last]; - + v_start = outline->points[first]; v_start.x = SCALED( v_start.x ); v_start.y = SCALED( v_start.y ); - v_last.x = SCALED( v_last.x ); - v_last.y = SCALED( v_last.y ); + v_last = outline->points[last]; + v_last.x = SCALED( v_last.x ); + v_last.y = SCALED( v_last.y ); v_control = v_start; @@ -1435,6 +1505,8 @@ tags--; } + QT_FT_TRACE5(( " move to (%.2f, %.2f)\n", + v_start.x / 64.0, v_start.y / 64.0 )); error = gray_move_to( &v_start, user ); if ( error ) goto Exit; @@ -1455,6 +1527,8 @@ vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); + QT_FT_TRACE5(( " line to (%.2f, %.2f)\n", + vec.x / 64.0, vec.y / 64.0 )); gray_render_line(user, UPSCALE(vec.x), UPSCALE(vec.y)); continue; } @@ -1480,6 +1554,10 @@ if ( tag == QT_FT_CURVE_TAG_ON ) { + QT_FT_TRACE5(( " conic to (%.2f, %.2f)" + " with control (%.2f, %.2f)\n", + vec.x / 64.0, vec.y / 64.0, + v_control.x / 64.0, v_control.y / 64.0 )); gray_render_conic(user, &v_control, &vec); continue; } @@ -1490,11 +1568,20 @@ v_middle.x = ( v_control.x + vec.x ) / 2; v_middle.y = ( v_control.y + vec.y ) / 2; + QT_FT_TRACE5(( " conic to (%.2f, %.2f)" + " with control (%.2f, %.2f)\n", + v_middle.x / 64.0, v_middle.y / 64.0, + v_control.x / 64.0, v_control.y / 64.0 )); gray_render_conic(user, &v_control, &v_middle); + v_control = vec; goto Do_Conic; } + QT_FT_TRACE5(( " conic to (%.2f, %.2f)" + " with control (%.2f, %.2f)\n", + v_start.x / 64.0, v_start.y / 64.0, + v_control.x / 64.0, v_control.y / 64.0 )); gray_render_conic(user, &v_control, &v_start); goto Close; } @@ -1525,10 +1612,20 @@ vec.x = SCALED( point->x ); vec.y = SCALED( point->y ); + QT_FT_TRACE5(( " cubic to (%.2f, %.2f)" + " with controls (%.2f, %.2f) and (%.2f, %.2f)\n", + vec.x / 64.0, vec.y / 64.0, + vec1.x / 64.0, vec1.y / 64.0, + vec2.x / 64.0, vec2.y / 64.0 )); gray_render_cubic(user, &vec1, &vec2, &vec); continue; } + QT_FT_TRACE5(( " cubic to (%.2f, %.2f)" + " with controls (%.2f, %.2f) and (%.2f, %.2f)\n", + v_start.x / 64.0, v_start.y / 64.0, + vec1.x / 64.0, vec1.y / 64.0, + vec2.x / 64.0, vec2.y / 64.0 )); gray_render_cubic(user, &vec1, &vec2, &v_start); goto Close; } @@ -1536,15 +1633,19 @@ } /* close the contour with a line segment */ + QT_FT_TRACE5(( " line to (%.2f, %.2f)\n", + v_start.x / 64.0, v_start.y / 64.0 )); gray_render_line(user, UPSCALE(v_start.x), UPSCALE(v_start.y)); Close: first = last + 1; } + QT_FT_TRACE5(( "FT_Outline_Decompose: Done\n", n )); return 0; Exit: + QT_FT_TRACE5(( "FT_Outline_Decompose: Error %d\n", error )); return error; Invalid_Outline: @@ -1557,7 +1658,6 @@ } TBand; - static int gray_convert_glyph_inner( RAS_ARG ) { @@ -1566,7 +1666,8 @@ if ( qt_ft_setjmp( ras.jump_buffer ) == 0 ) { error = QT_FT_Outline_Decompose( &ras.outline, &ras ); - gray_record_cell( RAS_VAR ); + if ( !ras.invalid ) + gray_record_cell( RAS_VAR ); } else { @@ -1608,29 +1709,12 @@ ras.count_ex = ras.max_ex - ras.min_ex; ras.count_ey = ras.max_ey - ras.min_ey; - /* simple heuristic used to speed-up the bezier decomposition -- see */ - /* the code in gray_render_conic() and gray_render_cubic() for more */ - /* details */ - ras.conic_level = 32; - ras.cubic_level = 16; - - { - int level = 0; - - - if ( ras.count_ex > 24 || ras.count_ey > 24 ) - level++; - if ( ras.count_ex > 120 || ras.count_ey > 120 ) - level++; - - ras.conic_level <<= level; - ras.cubic_level <<= level; - } - - /* setup vertical bands */ + /* set up vertical bands */ num_bands = (int)( ( ras.max_ey - ras.min_ey ) / ras.band_size ); - if ( num_bands == 0 ) num_bands = 1; - if ( num_bands >= 39 ) num_bands = 39; + if ( num_bands == 0 ) + num_bands = 1; + if ( num_bands >= 39 ) + num_bands = 39; ras.band_shoot = 0; @@ -1764,11 +1848,14 @@ if (raster->buffer_allocated_size < MINIMUM_POOL_SIZE ) return ErrRaster_OutOfMemory; + if ( !outline ) + return ErrRaster_Invalid_Outline; + /* return immediately if the outline is empty */ if ( outline->n_points == 0 || outline->n_contours <= 0 ) return 0; - if ( !outline || !outline->contours || !outline->points ) + if ( !outline->contours || !outline->points ) return ErrRaster_Invalid_Outline; if ( outline->n_points != diff --git a/src/gui/painting/qpagedpaintdevice.cpp b/src/gui/painting/qpagedpaintdevice.cpp index 22ec981134..1c7d6471b6 100644 --- a/src/gui/painting/qpagedpaintdevice.cpp +++ b/src/gui/painting/qpagedpaintdevice.cpp @@ -243,6 +243,18 @@ QPagedPaintDevicePrivate *QPagedPaintDevice::dd() Starts a new page. Returns \c true on success. */ +/*! + \enum QPagedPaintDevice::PdfVersion + + The PdfVersion enum describes the version of the PDF file that + is produced by QPrinter or QPdfWriter. + + \value PdfVersion_1_4 A PDF 1.4 compatible document is produced. + + \value PdfVersion_A1b A PDF/A-1b compatible document is produced. + + \since 5.10 +*/ /*! Sets the size of the a page to \a size. diff --git a/src/gui/painting/qpagedpaintdevice.h b/src/gui/painting/qpagedpaintdevice.h index c516f6a963..66dd6fa8cf 100644 --- a/src/gui/painting/qpagedpaintdevice.h +++ b/src/gui/painting/qpagedpaintdevice.h @@ -213,6 +213,8 @@ public: Envelope10 = Comm10E }; + enum PdfVersion { PdfVersion_1_4, PdfVersion_A1b }; + // ### Qt6 Make these virtual bool setPageLayout(const QPageLayout &pageLayout); bool setPageSize(const QPageSize &pageSize); diff --git a/src/gui/painting/qpaintengine.cpp b/src/gui/painting/qpaintengine.cpp index ddea168e72..f42fd4ff87 100644 --- a/src/gui/painting/qpaintengine.cpp +++ b/src/gui/painting/qpaintengine.cpp @@ -547,8 +547,8 @@ void qt_fill_tile(QPixmap *tile, const QPixmap &pixmap) } } -void qt_draw_tile(QPaintEngine *gc, qreal x, qreal y, qreal w, qreal h, - const QPixmap &pixmap, qreal xOffset, qreal yOffset) +Q_GUI_EXPORT void qt_draw_tile(QPaintEngine *gc, qreal x, qreal y, qreal w, qreal h, + const QPixmap &pixmap, qreal xOffset, qreal yOffset) { qreal yPos, xPos, drawH, drawW, yOff, xOff; yPos = y; @@ -929,11 +929,11 @@ QPoint QPaintEngine::coordinateOffset() const void QPaintEngine::setSystemClip(const QRegion ®ion) { Q_D(QPaintEngine); - d->systemClip = region; + d->baseSystemClip = region; // Be backward compatible and only call d->systemStateChanged() // if we currently have a system transform/viewport set. + d->updateSystemClip(); if (d->hasSystemTransform || d->hasSystemViewport) { - d->transformSystemClip(); d->systemStateChanged(); } } diff --git a/src/gui/painting/qpaintengine_p.h b/src/gui/painting/qpaintengine_p.h index 9d511f9bad..8ac3fcff5c 100644 --- a/src/gui/painting/qpaintengine_p.h +++ b/src/gui/painting/qpaintengine_p.h @@ -71,6 +71,7 @@ public: QPaintDevice *pdev; QPaintEngine *q_ptr; + QRegion baseSystemClip; QRegion systemClip; QRect systemRect; QRegion systemViewport; @@ -79,8 +80,9 @@ public: uint hasSystemTransform : 1; uint hasSystemViewport : 1; - inline void transformSystemClip() + inline void updateSystemClip() { + systemClip = baseSystemClip; if (systemClip.isEmpty()) return; @@ -104,15 +106,30 @@ public: inline void setSystemTransform(const QTransform &xform) { systemTransform = xform; - if ((hasSystemTransform = !xform.isIdentity()) || hasSystemViewport) - transformSystemClip(); - systemStateChanged(); + hasSystemTransform = !xform.isIdentity(); + updateSystemClip(); + if (q_ptr->state) + systemStateChanged(); } inline void setSystemViewport(const QRegion ®ion) { systemViewport = region; hasSystemViewport = !systemViewport.isEmpty(); + updateSystemClip(); + if (q_ptr->state) + systemStateChanged(); + } + + inline void setSystemTransformAndViewport(const QTransform &xform, const QRegion ®ion) + { + systemTransform = xform; + hasSystemTransform = !xform.isIdentity(); + systemViewport = region; + hasSystemViewport = !systemViewport.isEmpty(); + updateSystemClip(); + if (q_ptr->state) + systemStateChanged(); } virtual void systemStateChanged() { } diff --git a/src/gui/painting/qpdf.cpp b/src/gui/painting/qpdf.cpp index afeb198953..2b892159c5 100644 --- a/src/gui/painting/qpdf.cpp +++ b/src/gui/painting/qpdf.cpp @@ -42,16 +42,20 @@ #ifndef QT_NO_PDF #include "qplatformdefs.h" -#include <qdebug.h> -#include <qfile.h> -#include <qtemporaryfile.h> + +#include <private/qfont_p.h> #include <private/qmath_p.h> #include <private/qpainter_p.h> -#include <qnumeric.h> -#include "private/qfont_p.h" + +#include <qbuffer.h> +#include <qcryptographichash.h> +#include <qdatetime.h> +#include <qdebug.h> +#include <qfile.h> #include <qimagewriter.h> -#include "qbuffer.h" -#include "QtCore/qdatetime.h" +#include <qnumeric.h> +#include <qtemporaryfile.h> +#include <quuid.h> #ifndef QT_NO_COMPRESS #include <zlib.h> @@ -79,6 +83,45 @@ inline QPaintEngine::PaintEngineFeatures qt_pdf_decide_features() return f; } +extern bool qt_isExtendedRadialGradient(const QBrush &brush); + +// helper function to remove transparency from brush in PDF/A-1b mode +static void removeTransparencyFromBrush(QBrush &brush) +{ + if (brush.style() == Qt::SolidPattern) { + QColor color = brush.color(); + if (color.alpha() != 255) { + color.setAlpha(255); + brush.setColor(color); + } + + return; + } + + if (qt_isExtendedRadialGradient(brush)) { + brush = QBrush(Qt::black); // the safest we can do so far... + return; + } + + if (brush.style() == Qt::LinearGradientPattern + || brush.style() == Qt::RadialGradientPattern + || brush.style() == Qt::ConicalGradientPattern) { + + QGradientStops stops = brush.gradient()->stops(); + for (int i = 0; i < stops.size(); ++i) { + if (stops[i].second.alpha() != 255) + stops[i].second.setAlpha(255); + } + + const_cast<QGradient*>(brush.gradient())->setStops(stops); + return; + } + + if (brush.style() == Qt::TexturePattern) { + // handled inside QPdfEnginePrivate::addImage() already + return; + } +} /* also adds a space at the end of the number */ @@ -1042,7 +1085,22 @@ void QPdfEngine::updateState(const QPaintEngineState &state) d->stroker.matrix = state.transform(); if (flags & DirtyPen) { - d->pen = state.pen(); + if (d->pdfVersion == QPdfEngine::Version_A1b) { + QPen pen = state.pen(); + + QColor penColor = pen.color(); + if (penColor.alpha() != 255) + penColor.setAlpha(255); + pen.setColor(penColor); + + QBrush penBrush = pen.brush(); + removeTransparencyFromBrush(penBrush); + pen.setBrush(penBrush); + + d->pen = pen; + } else { + d->pen = state.pen(); + } d->hasPen = d->pen.style() != Qt::NoPen; d->stroker.setPen(d->pen, state.renderHints()); QBrush penBrush = d->pen.brush(); @@ -1054,7 +1112,13 @@ void QPdfEngine::updateState(const QPaintEngineState &state) d->stroker.setPen(d->pen, state.renderHints()); } if (flags & DirtyBrush) { - d->brush = state.brush(); + if (d->pdfVersion == QPdfEngine::Version_A1b) { + QBrush brush = state.brush(); + removeTransparencyFromBrush(brush); + d->brush = brush; + } else { + d->brush = state.brush(); + } if (d->brush.color().alpha() == 0 && d->brush.style() == Qt::SolidPattern) d->brush.setStyle(Qt::NoBrush); d->hasBrush = d->brush.style() != Qt::NoBrush; @@ -1286,6 +1350,12 @@ int QPdfEngine::resolution() const return d->resolution; } +void QPdfEngine::setPdfVersion(PdfVersion version) +{ + Q_D(QPdfEngine); + d->pdfVersion = version; +} + void QPdfEngine::setPageLayout(const QPageLayout &pageLayout) { Q_D(QPdfEngine); @@ -1364,6 +1434,7 @@ int QPdfEngine::metric(QPaintDevice::PaintDeviceMetric metricType) const QPdfEnginePrivate::QPdfEnginePrivate() : clipEnabled(false), allClipped(false), hasPen(true), hasBrush(false), simplePen(false), + pdfVersion(QPdfEngine::Version_1_4), outDevice(0), ownsDevice(false), embedFonts(true), grayscale(false), @@ -1465,16 +1536,38 @@ void QPdfEnginePrivate::writeHeader() addXrefEntry(0,false); xprintf("%%PDF-1.4\n"); + xprintf("%%\303\242\303\243\n"); writeInfo(); + int metaDataObj = -1; + int outputIntentObj = -1; + if (pdfVersion == QPdfEngine::Version_A1b) { + metaDataObj = writeXmpMetaData(); + outputIntentObj = writeOutputIntent(); + } + catalog = addXrefEntry(-1); pageRoot = requestObject(); - xprintf("<<\n" - "/Type /Catalog\n" - "/Pages %d 0 R\n" - ">>\n" - "endobj\n", pageRoot); + + // catalog + { + QByteArray catalog; + QPdf::ByteStream s(&catalog); + s << "<<\n" + << "/Type /Catalog\n" + << "/Pages " << pageRoot << "0 R\n"; + + if (pdfVersion == QPdfEngine::Version_A1b) { + s << "/OutputIntents [" << outputIntentObj << "0 R]\n"; + s << "/Metadata " << metaDataObj << "0 R\n"; + } + + s << ">>\n" + << "endobj\n"; + + write(catalog); + } // graphics state graphicsState = addXrefEntry(-1); @@ -1527,6 +1620,95 @@ void QPdfEnginePrivate::writeInfo() "endobj\n"); } +int QPdfEnginePrivate::writeXmpMetaData() +{ + const int metaDataObj = addXrefEntry(-1); + + const QString producer(QString::fromLatin1("Qt " QT_VERSION_STR)); + + const QDateTime now = QDateTime::currentDateTime(); + const QDate date = now.date(); + const QTime time = now.time(); + + QString timeStr; + timeStr.sprintf("%d-%02d-%02dT%02d:%02d:%02d", date.year(), date.month(), date.day(), + time.hour(), time.minute(), time.second()); + + const int offset = now.offsetFromUtc(); + const int hours = (offset / 60) / 60; + const int mins = (offset / 60) % 60; + QString tzStr; + if (offset < 0) + tzStr.sprintf("-%02d:%02d", -hours, -mins); + else if (offset > 0) + tzStr.sprintf("+%02d:%02d", hours , mins); + else + tzStr = QLatin1String("Z"); + + const QString metaDataDate = timeStr + tzStr; + + QFile metaDataFile(QLatin1String(":/qpdf/qpdfa_metadata.xml")); + metaDataFile.open(QIODevice::ReadOnly); + const QByteArray metaDataContent = QString::fromUtf8(metaDataFile.readAll()).arg(producer.toHtmlEscaped(), + title.toHtmlEscaped(), + creator.toHtmlEscaped(), + metaDataDate).toUtf8(); + xprintf("<<\n" + "/Type /Metadata /Subtype /XML\n" + "/Length %d\n" + ">>\n" + "stream\n", metaDataContent.size()); + write(metaDataContent); + xprintf("\nendstream\n" + "endobj\n"); + + return metaDataObj; +} + +int QPdfEnginePrivate::writeOutputIntent() +{ + const int colorProfile = addXrefEntry(-1); + { + QFile colorProfileFile(QLatin1String(":/qpdf/sRGB2014.icc")); + colorProfileFile.open(QIODevice::ReadOnly); + const QByteArray colorProfileData = colorProfileFile.readAll(); + + QByteArray data; + QPdf::ByteStream s(&data); + int length_object = requestObject(); + + s << "<<\n"; + s << "/N 3\n"; + s << "/Alternate /DeviceRGB\n"; + s << "/Length " << length_object << "0 R\n"; + s << "/Filter /FlateDecode\n"; + s << ">>\n"; + s << "stream\n"; + write(data); + const int len = writeCompressed(colorProfileData); + write("\nendstream\n" + "endobj\n"); + addXrefEntry(length_object); + xprintf("%d\n" + "endobj\n", len); + } + + const int outputIntent = addXrefEntry(-1); + { + xprintf("<<\n"); + xprintf("/Type /OutputIntent\n"); + xprintf("/S/GTS_PDFA1\n"); + xprintf("/OutputConditionIdentifier (sRGB_IEC61966-2-1_black_scaled)\n"); + xprintf("/DestOutputProfile %d 0 R\n", colorProfile); + xprintf("/Info(sRGB IEC61966 v2.1 with black scaling)\n"); + xprintf("/RegistryName(http://www.color.org)\n"); + xprintf(">>\n"); + xprintf("endobj\n"); + } + + return outputIntent; +} + void QPdfEnginePrivate::writePageRoot() { addXrefEntry(pageRoot); @@ -1568,6 +1750,7 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font) int fontstream = requestObject(); int cidfont = requestObject(); int toUnicode = requestObject(); + int cidset = requestObject(); QFontEngine::Properties properties = font->fontEngine->properties(); QByteArray postscriptName = properties.postscriptName.replace(' ', '_'); @@ -1597,7 +1780,8 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font) "/CapHeight " << properties.capHeight.toReal()*scale << "\n" "/StemV " << properties.lineWidth.toReal()*scale << "\n" "/FontFile2 " << fontstream << "0 R\n" - ">> endobj\n"; + "/CIDSet " << cidset << "0 R\n" + ">>\nendobj\n"; write(descriptor); } { @@ -1615,7 +1799,7 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font) "stream\n"; write(header); int len = writeCompressed(fontData); - write("endstream\n" + write("\nendstream\n" "endobj\n"); addXrefEntry(length_object); xprintf("%d\n" @@ -1642,7 +1826,7 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font) xprintf("<< /Length %d >>\n" "stream\n", touc.length()); write(touc); - write("endstream\n" + write("\nendstream\n" "endobj\n"); } { @@ -1659,6 +1843,29 @@ void QPdfEnginePrivate::embedFont(QFontSubset *font) "endobj\n"; write(font); } + { + QByteArray cidSetStream(font->nGlyphs() / 8 + 1, 0); + int byteCounter = 0; + int bitCounter = 0; + for (int i = 0; i < font->nGlyphs(); ++i) { + cidSetStream.data()[byteCounter] |= (1 << (7 - bitCounter)); + + bitCounter++; + if (bitCounter == 8) { + bitCounter = 0; + byteCounter++; + } + } + + addXrefEntry(cidset); + xprintf("<<\n"); + xprintf("/Length %d\n", cidSetStream.size()); + xprintf(">>\n"); + xprintf("stream\n"); + write(cidSetStream); + xprintf("\nendstream\n"); + xprintf("endobj\n"); + } } @@ -1748,7 +1955,7 @@ void QPdfEnginePrivate::writePage() xprintf("stream\n"); QIODevice *content = currentPage->stream(); int len = writeCompressed(content); - xprintf("endstream\n" + xprintf("\nendstream\n" "endobj\n"); addXrefEntry(pageStreamLength); @@ -1768,15 +1975,28 @@ void QPdfEnginePrivate::writeTail() for (int i = 1; i < xrefPositions.size()-1; ++i) xprintf("%010d 00000 n \n", xrefPositions[i]); - xprintf("trailer\n" - "<<\n" - "/Size %d\n" - "/Info %d 0 R\n" - "/Root %d 0 R\n" - ">>\n" - "startxref\n%d\n" - "%%%%EOF\n", - xrefPositions.size()-1, info, catalog, xrefPositions.constLast()); + { + QByteArray trailer; + QPdf::ByteStream s(&trailer); + + s << "trailer\n" + << "<<\n" + << "/Size " << xrefPositions.size() - 1 << "\n" + << "/Info " << info << "0 R\n" + << "/Root " << catalog << "0 R\n"; + + if (pdfVersion == QPdfEngine::Version_A1b) { + const QString uniqueId = QUuid::createUuid().toString(); + const QByteArray fileIdentifier = QCryptographicHash::hash(uniqueId.toLatin1(), QCryptographicHash::Md5).toHex(); + s << "/ID [ <" << fileIdentifier << "> <" << fileIdentifier << "> ]\n"; + } + + s << ">>\n" + << "startxref\n" << xrefPositions.constLast() << "\n" + << "%%EOF\n"; + + write(trailer); + } } int QPdfEnginePrivate::addXrefEntry(int object, bool printostr) @@ -1794,7 +2014,13 @@ int QPdfEnginePrivate::addXrefEntry(int object, bool printostr) return object; } -void QPdfEnginePrivate::printString(const QString &string) { +void QPdfEnginePrivate::printString(const QString &string) +{ + if (string.isEmpty()) { + write("()"); + return; + } + // The 'text string' type in PDF is encoded either as PDFDocEncoding, or // Unicode UTF-16 with a Unicode byte order mark as the first character // (0xfeff), with the high-order byte first. @@ -1976,7 +2202,7 @@ int QPdfEnginePrivate::writeImage(const QByteArray &data, int width, int height, xprintf(">>\nstream\n"); len = writeCompressed(data); } - xprintf("endstream\n" + xprintf("\nendstream\n" "endobj\n"); addXrefEntry(lenobj); xprintf("%d\n" @@ -2303,7 +2529,7 @@ int QPdfEnginePrivate::gradientBrush(const QBrush &b, const QTransform &matrix, ">>\n" "stream\n" << content - << "endstream\n" + << "\nendstream\n" "endobj\n"; int softMaskFormObject = addXrefEntry(-1); @@ -2412,7 +2638,7 @@ int QPdfEnginePrivate::addBrushPattern(const QTransform &m, bool *specifyColor, ">>\n" "stream\n" << pattern - << "endstream\n" + << "\nendstream\n" "endobj\n"; int patternObj = addXrefEntry(-1); @@ -2443,6 +2669,23 @@ int QPdfEnginePrivate::addImage(const QImage &img, bool *bitmap, qint64 serial_n QImage image = img; QImage::Format format = image.format(); + + if (pdfVersion == QPdfEngine::Version_A1b) { + if (image.hasAlphaChannel()) { + // transparent images are not allowed in PDF/A-1b, so we convert it to + // a format without alpha channel first + + QImage alphaLessImage(image.width(), image.height(), QImage::Format_RGB32); + alphaLessImage.fill(Qt::white); + + QPainter p(&alphaLessImage); + p.drawImage(0, 0, image); + + image = alphaLessImage; + format = image.format(); + } + } + if (image.depth() == 1 && *bitmap && is_monochrome(img.colorTable())) { if (format == QImage::Format_MonoLSB) image = image.convertToFormat(QImage::Format_Mono); diff --git a/src/gui/painting/qpdf.qrc b/src/gui/painting/qpdf.qrc new file mode 100644 index 0000000000..56359c775b --- /dev/null +++ b/src/gui/painting/qpdf.qrc @@ -0,0 +1,7 @@ +<!DOCTYPE RCC> +<RCC version="1.0"> + <qresource prefix="qpdf/"> + <file>qpdfa_metadata.xml</file> + <file alias="sRGB2014.icc">../../3rdparty/icc/sRGB2014.icc</file> + </qresource> +</RCC> diff --git a/src/gui/painting/qpdf_p.h b/src/gui/painting/qpdf_p.h index a6aa2940c8..f5bb4e17a8 100644 --- a/src/gui/painting/qpdf_p.h +++ b/src/gui/painting/qpdf_p.h @@ -168,6 +168,12 @@ class Q_GUI_EXPORT QPdfEngine : public QPaintEngine Q_DECLARE_PRIVATE(QPdfEngine) friend class QPdfWriter; public: + enum PdfVersion + { + Version_1_4, + Version_A1b + }; + QPdfEngine(); QPdfEngine(QPdfEnginePrivate &d); ~QPdfEngine() {} @@ -177,6 +183,8 @@ public: void setResolution(int resolution); int resolution() const; + void setPdfVersion(PdfVersion version); + // reimplementations QPaintEngine bool begin(QPaintDevice *pdev) Q_DECL_OVERRIDE; bool end() Q_DECL_OVERRIDE; @@ -258,6 +266,7 @@ public: bool hasBrush; bool simplePen; qreal opacity; + QPdfEngine::PdfVersion pdfVersion; QHash<QFontEngine::FaceId, QFontSubset *> fonts; @@ -286,6 +295,8 @@ private: int createShadingFunction(const QGradient *gradient, int from, int to, bool reflect, bool alpha); void writeInfo(); + int writeXmpMetaData(); + int writeOutputIntent(); void writePageRoot(); void writeFonts(); void embedFont(QFontSubset *font); diff --git a/src/gui/painting/qpdfa_metadata.xml b/src/gui/painting/qpdfa_metadata.xml new file mode 100644 index 0000000000..5e5c57f1c6 --- /dev/null +++ b/src/gui/painting/qpdfa_metadata.xml @@ -0,0 +1,16 @@ +<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?> +<x:xmpmeta xmlns:x="adobe:ns:meta/"> + <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> + <rdf:Description xmlns:dc="http://purl.org/dc/elements/1.1/" rdf:about=""> + <dc:title> + <rdf:Alt> + <rdf:li xml:lang="x-default">%2</rdf:li> + </rdf:Alt> + </dc:title> + </rdf:Description> + <rdf:Description xmlns:xmp="http://ns.adobe.com/xap/1.0/" rdf:about="" xmp:CreatorTool="%3" xmp:CreateDate="%4" xmp:ModifyDate="%4"/> + <rdf:Description xmlns:pdf="http://ns.adobe.com/pdf/1.3/" rdf:about="" pdf:Producer="%1"/> + <rdf:Description xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/" rdf:about="" pdfaid:part="1" pdfaid:conformance="B"/> + </rdf:RDF> +</x:xmpmeta> +<?xpacket end='w'?> diff --git a/src/gui/painting/qpdfwriter.cpp b/src/gui/painting/qpdfwriter.cpp index edf2950a67..5af465edeb 100644 --- a/src/gui/painting/qpdfwriter.cpp +++ b/src/gui/painting/qpdfwriter.cpp @@ -56,6 +56,7 @@ public: { engine = new QPdfEngine(); output = 0; + pdfVersion = QPdfWriter::PdfVersion_1_4; } ~QPdfWriterPrivate() { @@ -65,6 +66,7 @@ public: QPdfEngine *engine; QFile *output; + QPdfWriter::PdfVersion pdfVersion; }; class QPdfPagedPaintDevicePrivate : public QPagedPaintDevicePrivate @@ -177,6 +179,35 @@ QPdfWriter::~QPdfWriter() } /*! + \since 5.10 + + Sets the PDF version for this writer to \a version. + + If \a version is the same value as currently set then no change will be made. +*/ +void QPdfWriter::setPdfVersion(PdfVersion version) +{ + Q_D(QPdfWriter); + + if (d->pdfVersion == version) + return; + + d->pdfVersion = version; + d->engine->setPdfVersion(d->pdfVersion == QPdfWriter::PdfVersion_1_4 ? QPdfEngine::Version_1_4 : QPdfEngine::Version_A1b); +} + +/*! + \since 5.10 + + Returns the PDF version for this writer. The default is \c PdfVersion_1_4. +*/ +QPdfWriter::PdfVersion QPdfWriter::pdfVersion() const +{ + Q_D(const QPdfWriter); + return d->pdfVersion; +} + +/*! Returns the title of the document. */ QString QPdfWriter::title() const diff --git a/src/gui/painting/qpdfwriter.h b/src/gui/painting/qpdfwriter.h index 17c73dd480..b260805b2b 100644 --- a/src/gui/painting/qpdfwriter.h +++ b/src/gui/painting/qpdfwriter.h @@ -61,6 +61,9 @@ public: explicit QPdfWriter(QIODevice *device); ~QPdfWriter(); + void setPdfVersion(PdfVersion version); + PdfVersion pdfVersion() const; + QString title() const; void setTitle(const QString &title); diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp index b8bbdefa37..e006fad437 100644 --- a/src/gui/painting/qplatformbackingstore.cpp +++ b/src/gui/painting/qplatformbackingstore.cpp @@ -70,6 +70,13 @@ #define GL_UNSIGNED_INT_2_10_10_10_REV 0x8368 #endif +#ifndef GL_FRAMEBUFFER_SRB +#define GL_FRAMEBUFFER_SRGB 0x8DB9 +#endif +#ifndef GL_FRAMEBUFFER_SRGB_CAPABLE +#define GL_FRAMEBUFFER_SRGB_CAPABLE 0x8DBA +#endif + QT_BEGIN_NAMESPACE class QPlatformBackingStorePrivate @@ -269,7 +276,7 @@ static inline QRect toBottomLeftRect(const QRect &topLeftRect, int windowHeight) } static void blitTextureForWidget(const QPlatformTextureList *textures, int idx, QWindow *window, const QRect &deviceWindowRect, - QOpenGLTextureBlitter *blitter, const QPoint &offset) + QOpenGLTextureBlitter *blitter, const QPoint &offset, bool canUseSrgb) { const QRect clipRect = textures->clipRect(idx); if (clipRect.isEmpty()) @@ -289,7 +296,15 @@ static void blitTextureForWidget(const QPlatformTextureList *textures, int idx, deviceRect(rectInWindow, window).size(), QOpenGLTextureBlitter::OriginBottomLeft); + QOpenGLFunctions *funcs = QOpenGLContext::currentContext()->functions(); + const bool srgb = textures->flags(idx).testFlag(QPlatformTextureList::TextureIsSrgb); + if (srgb && canUseSrgb) + funcs->glEnable(GL_FRAMEBUFFER_SRGB); + blitter->blit(textures->textureId(idx), target, source); + + if (srgb && canUseSrgb) + funcs->glDisable(GL_FRAMEBUFFER_SRGB); } /*! @@ -334,10 +349,23 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion ®i const QRect deviceWindowRect = deviceRect(QRect(QPoint(), window->size()), window); + bool canUseSrgb = false; + // If there are any sRGB textures in the list, check if the destination + // framebuffer is sRGB capable. + for (int i = 0; i < textures->count(); ++i) { + if (textures->flags(i).testFlag(QPlatformTextureList::TextureIsSrgb)) { + GLint cap = 0; + funcs->glGetIntegerv(GL_FRAMEBUFFER_SRGB_CAPABLE, &cap); + if (cap) + canUseSrgb = true; + break; + } + } + // Textures for renderToTexture widgets. for (int i = 0; i < textures->count(); ++i) { if (!textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) - blitTextureForWidget(textures, i, window, deviceWindowRect, d_ptr->blitter, offset); + blitTextureForWidget(textures, i, window, deviceWindowRect, d_ptr->blitter, offset, canUseSrgb); } // Backingstore texture with the normal widgets. @@ -406,7 +434,7 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion ®i // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set. for (int i = 0; i < textures->count(); ++i) { if (textures->flags(i).testFlag(QPlatformTextureList::StacksOnTop)) - blitTextureForWidget(textures, i, window, deviceWindowRect, d_ptr->blitter, offset); + blitTextureForWidget(textures, i, window, deviceWindowRect, d_ptr->blitter, offset, canUseSrgb); } funcs->glDisable(GL_BLEND); diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h index ec56aaa002..d1ce67a65d 100644 --- a/src/gui/painting/qplatformbackingstore.h +++ b/src/gui/painting/qplatformbackingstore.h @@ -78,7 +78,8 @@ class Q_GUI_EXPORT QPlatformTextureList : public QObject Q_DECLARE_PRIVATE(QPlatformTextureList) public: enum Flag { - StacksOnTop = 0x01 + StacksOnTop = 0x01, + TextureIsSrgb = 0x02 }; Q_DECLARE_FLAGS(Flags, Flag) diff --git a/src/gui/painting/qpolygon.cpp b/src/gui/painting/qpolygon.cpp index 126b752811..3bf6004fcc 100644 --- a/src/gui/painting/qpolygon.cpp +++ b/src/gui/painting/qpolygon.cpp @@ -909,6 +909,8 @@ QPolygon QPolygon::united(const QPolygon &r) const Set operations on polygons will treat the polygons as areas. Non-closed polygons will be treated as implicitly closed. + + \sa intersects() */ QPolygon QPolygon::intersected(const QPolygon &r) const @@ -938,6 +940,26 @@ QPolygon QPolygon::subtracted(const QPolygon &r) const } /*! + \since 5.10 + + Returns \c true if the current polygon intersects at any point the given polygon \a p. + Also returns \c true if the current polygon contains or is contained by any part of \a p. + + Set operations on polygons will treat the polygons as + areas. Non-closed polygons will be treated as implicitly closed. + + \sa intersected() +*/ + +bool QPolygon::intersects(const QPolygon &p) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(p); + + return subject.intersects(clip); +} + +/*! \since 4.3 Returns a polygon which is the union of this polygon and \a r. @@ -964,6 +986,7 @@ QPolygonF QPolygonF::united(const QPolygonF &r) const Set operations on polygons will treat the polygons as areas. Non-closed polygons will be treated as implicitly closed. + \sa intersects() */ QPolygonF QPolygonF::intersected(const QPolygonF &r) const @@ -992,6 +1015,26 @@ QPolygonF QPolygonF::subtracted(const QPolygonF &r) const } /*! + \since 5.10 + + Returns \c true if the current polygon intersects at any point the given polygon \a p. + Also returns \c true if the current polygon contains or is contained by any part of \a p. + + Set operations on polygons will treat the polygons as + areas. Non-closed polygons will be treated as implicitly closed. + + \sa intersected() +*/ + +bool QPolygonF::intersects(const QPolygonF &p) const +{ + QPainterPath subject; subject.addPolygon(*this); + QPainterPath clip; clip.addPolygon(p); + + return subject.intersects(clip); +} + +/*! Returns the polygon as a QVariant. */ diff --git a/src/gui/painting/qpolygon.h b/src/gui/painting/qpolygon.h index f192e8d10b..8e74a499fd 100644 --- a/src/gui/painting/qpolygon.h +++ b/src/gui/painting/qpolygon.h @@ -98,6 +98,8 @@ public: Q_REQUIRED_RESULT QPolygon united(const QPolygon &r) const; Q_REQUIRED_RESULT QPolygon intersected(const QPolygon &r) const; Q_REQUIRED_RESULT QPolygon subtracted(const QPolygon &r) const; + + bool intersects(const QPolygon &r) const; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QPolygon) @@ -175,6 +177,8 @@ public: Q_REQUIRED_RESULT QPolygonF united(const QPolygonF &r) const; Q_REQUIRED_RESULT QPolygonF intersected(const QPolygonF &r) const; Q_REQUIRED_RESULT QPolygonF subtracted(const QPolygonF &r) const; + + bool intersects(const QPolygonF &r) const; }; Q_DECLARE_SHARED_NOT_MOVABLE_UNTIL_QT6(QPolygonF) |