diff options
author | Alexis Menard <alexis.menard@nokia.com> | 2009-04-17 14:06:06 (GMT) |
---|---|---|
committer | Alexis Menard <alexis.menard@nokia.com> | 2009-04-17 14:06:06 (GMT) |
commit | f15b8a83e2e51955776a3f07cb85ebfc342dd8ef (patch) | |
tree | c5dc684986051654898db11ce73e03b9fec8db99 /src/gui/painting/qpaintengine_raster.cpp | |
download | Qt-f15b8a83e2e51955776a3f07cb85ebfc342dd8ef.zip Qt-f15b8a83e2e51955776a3f07cb85ebfc342dd8ef.tar.gz Qt-f15b8a83e2e51955776a3f07cb85ebfc342dd8ef.tar.bz2 |
Initial import of statemachine branch from the old kinetic repository
Diffstat (limited to 'src/gui/painting/qpaintengine_raster.cpp')
-rw-r--r-- | src/gui/painting/qpaintengine_raster.cpp | 6058 |
1 files changed, 6058 insertions, 0 deletions
diff --git a/src/gui/painting/qpaintengine_raster.cpp b/src/gui/painting/qpaintengine_raster.cpp new file mode 100644 index 0000000..495e6c4 --- /dev/null +++ b/src/gui/painting/qpaintengine_raster.cpp @@ -0,0 +1,6058 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain +** additional rights. These rights are described in the Nokia Qt LGPL +** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this +** package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/qglobal.h> + +#define QT_FT_BEGIN_HEADER +#define QT_FT_END_HEADER + +#include <private/qrasterdefs_p.h> +#include <private/qgrayraster_p.h> + +#include <qpainterpath.h> +#include <qdebug.h> +#include <qhash.h> +#include <qlabel.h> +#include <qbitmap.h> +#include <qmath.h> + +#if defined (Q_WS_X11) +# include <private/qfontengine_ft_p.h> +#endif + +// #include <private/qdatabuffer_p.h> +// #include <private/qpainter_p.h> +#include <private/qmath_p.h> +#include <private/qtextengine_p.h> +#include <private/qfontengine_p.h> +#include <private/qpixmap_raster_p.h> +// #include <private/qpolygonclipper_p.h> +// #include <private/qrasterizer_p.h> +#include <private/qimage_p.h> + +#include "qpaintengine_raster_p.h" +// #include "qbezier_p.h" +#include "qoutlinemapper_p.h" + +#if defined(Q_WS_WIN) +# include <qt_windows.h> +# include <qvarlengtharray.h> +# include <private/qfontengine_p.h> +# if defined(Q_OS_WINCE) +# include "qguifunctions_wince.h" +# endif +#elif defined(Q_WS_MAC) +# include <private/qt_mac_p.h> +# include <private/qpixmap_mac_p.h> +# include <private/qpaintengine_mac_p.h> +#elif defined(Q_WS_QWS) +# if !defined(QT_NO_FREETYPE) +# include <private/qfontengine_ft_p.h> +# endif +# if !defined(QT_NO_QWS_QPF2) +# include <private/qfontengine_qpf_p.h> +# endif +# include <private/qabstractfontengine_p.h> +#endif + +#if defined(Q_WS_WIN64) +# include <malloc.h> +#endif +#include <limits.h> + +#if defined(QT_NO_FPU) || (_MSC_VER >= 1300 && _MSC_VER < 1400) +# define FLOATING_POINT_BUGGY_OR_NO_FPU +#endif + +QT_BEGIN_NAMESPACE + +#define qreal_to_fixed_26_6(f) (int(f * 64)) +#define qt_swap_int(x, y) { int tmp = (x); (x) = (y); (y) = tmp; } +#define qt_swap_qreal(x, y) { qreal tmp = (x); (x) = (y); (y) = tmp; } + +#ifdef Q_WS_WIN +static bool qt_enable_16bit_colors = false; +#endif + +// #define QT_DEBUG_DRAW +#ifdef QT_DEBUG_DRAW +void dumpClip(int width, int height, QClipData *clip); +#endif + +#define QT_FAST_SPANS + + +// A little helper macro to get a better approximation of dimensions. +// If we have a rect that starting at 0.5 of width 3.5 it should span +// 4 pixels. +#define int_dim(pos, dim) (int(pos+dim) - int(pos)) + +// use the same rounding as in qrasterizer.cpp (6 bit fixed point) +static const qreal aliasedCoordinateDelta = 0.5 - 0.015625; + +#ifdef Q_WS_WIN +extern bool qt_cleartype_enabled; +#endif + + +/******************************************************************************** + * Span functions + */ +static void qt_span_fill_clipRect(int count, const QSpan *spans, void *userData); +static void qt_span_fill_clipRegion(int count, const QSpan *spans, void *userData); +static void qt_span_fill_clipped(int count, const QSpan *spans, void *userData); +static void qt_span_clip(int count, const QSpan *spans, void *userData); +static void qt_merge_clip(const QClipData *c1, const QClipData *c2, QClipData *result); + +struct ClipData +{ + QClipData *oldClip; + QClipData *newClip; + Qt::ClipOperation operation; +}; + +enum LineDrawMode { + LineDrawClipped, + LineDrawNormal, + LineDrawIncludeLastPixel +}; + +static void drawLine_midpoint_i(int x1, int y1, int x2, int y2, ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &rect); +static void drawLine_midpoint_dashed_i(int x1, int y1, int x2, int y2, + QPen *pen, ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &devRect, + int *patternOffset); +// static void drawLine_midpoint_f(qreal x1, qreal y1, qreal x2, qreal y2, +// ProcessSpans span_func, QSpanData *data, +// LineDrawMode style, const QRect &devRect); + +static void drawEllipse_midpoint_i(const QRect &rect, const QRect &clip, + ProcessSpans pen_func, ProcessSpans brush_func, + QSpanData *pen_data, QSpanData *brush_data); + +struct QRasterFloatPoint { + qreal x; + qreal y; +}; + +#ifdef QT_DEBUG_DRAW +static const QRectF boundingRect(const QPointF *points, int pointCount) +{ + const QPointF *e = points; + const QPointF *last = points + pointCount; + qreal minx, maxx, miny, maxy; + minx = maxx = e->x(); + miny = maxy = e->y(); + while (++e < last) { + if (e->x() < minx) + minx = e->x(); + else if (e->x() > maxx) + maxx = e->x(); + if (e->y() < miny) + miny = e->y(); + else if (e->y() > maxy) + maxy = e->y(); + } + return QRectF(QPointF(minx, miny), QPointF(maxx, maxy)); +} +#endif + +template <typename T> static inline bool isRect(const T *pts, int elementCount) { + return (elementCount == 5 // 5-point polygon, check for closed rect + && pts[0] == pts[8] && pts[1] == pts[9] // last point == first point + && pts[0] == pts[6] && pts[2] == pts[4] // x values equal + && pts[1] == pts[3] && pts[5] == pts[7] // y values equal... + && pts[0] < pts[4] && pts[1] < pts[5] + ) || + (elementCount == 4 // 4-point polygon, check for unclosed rect + && pts[0] == pts[6] && pts[2] == pts[4] // x values equal + && pts[1] == pts[3] && pts[5] == pts[7] // y values equal... + && pts[0] < pts[4] && pts[1] < pts[5] + ); +} + + +static void qt_ft_outline_move_to(qfixed x, qfixed y, void *data) +{ + ((QOutlineMapper *) data)->moveTo(QPointF(qt_fixed_to_real(x), qt_fixed_to_real(y))); +} + +static void qt_ft_outline_line_to(qfixed x, qfixed y, void *data) +{ + ((QOutlineMapper *) data)->lineTo(QPointF(qt_fixed_to_real(x), qt_fixed_to_real(y))); +} + +static void qt_ft_outline_cubic_to(qfixed c1x, qfixed c1y, + qfixed c2x, qfixed c2y, + qfixed ex, qfixed ey, + void *data) +{ + ((QOutlineMapper *) data)->curveTo(QPointF(qt_fixed_to_real(c1x), qt_fixed_to_real(c1y)), + QPointF(qt_fixed_to_real(c2x), qt_fixed_to_real(c2y)), + QPointF(qt_fixed_to_real(ex), qt_fixed_to_real(ey))); +} + + +#if !defined(QT_NO_DEBUG) && 0 +static void qt_debug_path(const QPainterPath &path) +{ + const char *names[] = { + "MoveTo ", + "LineTo ", + "CurveTo ", + "CurveToData" + }; + + fprintf(stderr,"\nQPainterPath: elementCount=%d\n", path.elementCount()); + for (int i=0; i<path.elementCount(); ++i) { + const QPainterPath::Element &e = path.elementAt(i); + Q_ASSERT(e.type >= 0 && e.type <= QPainterPath::CurveToDataElement); + fprintf(stderr," - %3d:: %s, (%.2f, %.2f)\n", i, names[e.type], e.x, e.y); + } +} +#endif + + + +/*! + \class QRasterPaintEngine + \preliminary + \ingroup qws + \since 4.2 + + \brief The QRasterPaintEngine class enables hardware acceleration + of painting operations in Qt for Embedded Linux. + + Note that this functionality is only available in + \l{Qt for Embedded Linux}. + + In \l{Qt for Embedded Linux}, painting is a pure software + implementation. But starting with Qt 4.2, it is + possible to add an accelerated graphics driver to take advantage + of available hardware resources. + + Hardware acceleration is accomplished by creating a custom screen + driver, accelerating the copying from memory to the screen, and + implementing a custom paint engine accelerating the various + painting operations. Then a custom paint device (derived from the + QCustomRasterPaintDevice class) and a custom window surface + (derived from QWSWindowSurface) must be implemented to make + \l{Qt for Embedded Linux} aware of the accelerated driver. + + \note The QRasterPaintEngine class does not support 8-bit images. + Instead, they need to be converted to a supported format, such as + QImage::Format_ARGB32_Premultiplied. + + See the \l {Adding an Accelerated Graphics Driver to Qt for Embedded Linux} + documentation for details. + + \sa QCustomRasterPaintDevice, QPaintEngine +*/ + +/*! + \fn Type QRasterPaintEngine::type() const + \reimp +*/ + +/*! + \typedef QSpan + \relates QRasterPaintEngine + + A struct equivalent to QT_FT_Span, containing a position (x, + y), the span's length in pixels and its color/coverage (a value + ranging from 0 to 255). +*/ + +/*! + \since 4.5 + + Creates a raster based paint engine for operating on the given + \a device, with the complete set of \l + {QPaintEngine::PaintEngineFeature}{paint engine features and + capabilities}. +*/ +QRasterPaintEngine::QRasterPaintEngine(QPaintDevice *device) + : QPaintEngineEx(*(new QRasterPaintEnginePrivate)) +{ + d_func()->device = device; + init(); +} + +/*! + \internal +*/ +QRasterPaintEngine::QRasterPaintEngine(QRasterPaintEnginePrivate &dd, QPaintDevice *device) + : QPaintEngineEx(dd) +{ + d_func()->device = device; + init(); +} + +void QRasterPaintEngine::init() +{ + Q_D(QRasterPaintEngine); + + +#ifdef Q_WS_WIN + d->hdc = 0; +#endif + + d->rasterPoolSize = 8192; + d->rasterPoolBase = +#if defined(Q_WS_WIN64) + // We make use of setjmp and longjmp in qgrayraster.c which requires + // 16-byte alignment, hence we hardcode this requirement here.. + (unsigned char *) _aligned_malloc(d->rasterPoolSize, sizeof(void*) * 2); +#else + (unsigned char *) malloc(d->rasterPoolSize); +#endif + + // The antialiasing raster. + d->grayRaster = new QT_FT_Raster; + qt_ft_grays_raster.raster_new(0, d->grayRaster); + qt_ft_grays_raster.raster_reset(*d->grayRaster, d->rasterPoolBase, d->rasterPoolSize); + + d->rasterizer = new QRasterizer; + d->rasterBuffer = new QRasterBuffer(); + d->outlineMapper = new QOutlineMapper; + d->outlinemapper_xform_dirty = true; + + d->basicStroker.setMoveToHook(qt_ft_outline_move_to); + d->basicStroker.setLineToHook(qt_ft_outline_line_to); + d->basicStroker.setCubicToHook(qt_ft_outline_cubic_to); + d->dashStroker = 0; + + d->baseClip = 0; + + d->image_filler.init(d->rasterBuffer, this); + d->image_filler.type = QSpanData::Texture; + + d->image_filler_xform.init(d->rasterBuffer, this); + d->image_filler_xform.type = QSpanData::Texture; + + d->solid_color_filler.init(d->rasterBuffer, this); + d->solid_color_filler.type = QSpanData::Solid; + + d->deviceDepth = d->device->depth(); + + d->mono_surface = false; + gccaps &= ~PorterDuff; + + QImage::Format format = QImage::Format_Invalid; + + switch (d->device->devType()) { + case QInternal::Pixmap: + qWarning("QRasterPaintEngine: unsupported for pixmaps..."); + break; + case QInternal::Image: + format = d->rasterBuffer->prepare(static_cast<QImage *>(d->device)); + break; +#ifdef Q_WS_QWS + case QInternal::CustomRaster: + d->rasterBuffer->prepare(static_cast<QCustomRasterPaintDevice*>(d->device)); + break; +#endif + default: + qWarning("QRasterPaintEngine: unsupported target device %d\n", d->device->devType()); + d->device = 0; + return; + } + + switch (format) { + case QImage::Format_MonoLSB: + case QImage::Format_Mono: + d->mono_surface = true; + break; + case QImage::Format_ARGB8565_Premultiplied: + case QImage::Format_ARGB8555_Premultiplied: + case QImage::Format_ARGB6666_Premultiplied: + case QImage::Format_ARGB4444_Premultiplied: + case QImage::Format_ARGB32_Premultiplied: + case QImage::Format_ARGB32: + gccaps |= PorterDuff; + break; + case QImage::Format_RGB32: + case QImage::Format_RGB444: + case QImage::Format_RGB555: + case QImage::Format_RGB666: + case QImage::Format_RGB888: + case QImage::Format_RGB16: + break; + default: + break; + } +} + + + + +/*! + Destroys this paint engine. +*/ +QRasterPaintEngine::~QRasterPaintEngine() +{ + Q_D(QRasterPaintEngine); + +#if defined(Q_WS_WIN64) + _aligned_free(d->rasterPoolBase); +#else + free(d->rasterPoolBase); +#endif + + qt_ft_grays_raster.raster_done(*d->grayRaster); + delete d->grayRaster; + + delete d->rasterBuffer; + delete d->outlineMapper; + delete d->rasterizer; + delete d->dashStroker; +} + +/*! + \reimp +*/ +bool QRasterPaintEngine::begin(QPaintDevice *device) +{ + Q_D(QRasterPaintEngine); + + if (device->devType() == QInternal::Pixmap) { + QPixmap *pixmap = static_cast<QPixmap *>(device); + if (pixmap->data->classId() == QPixmapData::RasterClass) + d->device = pixmap->data->buffer(); + } else { + d->device = device; + } + + // Make sure QPaintEngine::paintDevice() returns the proper device. + d->pdev = d->device; + + Q_ASSERT(d->device->devType() == QInternal::Image + || d->device->devType() == QInternal::CustomRaster); + + d->systemStateChanged(); + + QRasterPaintEngineState *s = state(); + ensureOutlineMapper(); + d->outlineMapper->m_clip_rect = d->deviceRect.adjusted(-10, -10, 10, 10); + QRect bounds(-QT_RASTER_COORD_LIMIT, -QT_RASTER_COORD_LIMIT, + 2*QT_RASTER_COORD_LIMIT, 2*QT_RASTER_COORD_LIMIT); + d->outlineMapper->m_clip_rect = bounds.intersected(d->outlineMapper->m_clip_rect); + + + d->rasterizer->setClipRect(d->deviceRect); + + s->penData.init(d->rasterBuffer, this); + s->penData.setup(s->pen.brush(), s->intOpacity); + s->stroker = &d->basicStroker; + d->basicStroker.setClipRect(d->deviceRect); + + s->brushData.init(d->rasterBuffer, this); + s->brushData.setup(s->brush, s->intOpacity); + + d->rasterBuffer->compositionMode = QPainter::CompositionMode_SourceOver; + + setDirty(DirtyBrushOrigin); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::begin(" << (void *) device + << ") devType:" << device->devType() + << "devRect:" << d->deviceRect; + if (d->baseClip) { + dumpClip(d->rasterBuffer->width(), d->rasterBuffer->height(), d->baseClip); + } +#endif + +#if defined(Q_WS_WIN) + d->isPlain45DegreeRotation = true; +#endif + + if (d->mono_surface) + d->glyphCacheType = QFontEngineGlyphCache::Raster_Mono; +#ifdef Q_WS_WIN + else if (qt_cleartype_enabled) { + QImage::Format format = static_cast<QImage *>(d->device)->format(); + if (format == QImage::Format_ARGB32_Premultiplied || format == QImage::Format_RGB32) + d->glyphCacheType = QFontEngineGlyphCache::Raster_RGBMask; + else + d->glyphCacheType = QFontEngineGlyphCache::Raster_A8; + } +#endif + else + d->glyphCacheType = QFontEngineGlyphCache::Raster_A8; + + setActive(true); + return true; +} + +/*! + \reimp +*/ +bool QRasterPaintEngine::end() +{ + Q_D(QRasterPaintEngine); +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::end devRect:" << d->deviceRect; + if (d->baseClip) { + dumpClip(d->rasterBuffer->width(), d->rasterBuffer->height(), d->baseClip); + } +#endif + + if (d->baseClip) { + delete d->baseClip; + d->baseClip = 0; + } + + return true; +} + +/*! + \internal +*/ +void QRasterPaintEngine::releaseBuffer() +{ + Q_D(QRasterPaintEngine); + delete d->rasterBuffer; + d->rasterBuffer = new QRasterBuffer; +} + +/*! + \internal +*/ +QSize QRasterPaintEngine::size() const +{ + Q_D(const QRasterPaintEngine); + return QSize(d->rasterBuffer->width(), d->rasterBuffer->height()); +} + +/*! + \internal +*/ +#ifndef QT_NO_DEBUG +void QRasterPaintEngine::saveBuffer(const QString &s) const +{ + Q_D(const QRasterPaintEngine); + d->rasterBuffer->bufferImage().save(s, "PNG"); +} +#endif + +/*! + \internal +*/ +void QRasterPaintEngine::updateMatrix(const QTransform &matrix) +{ + QRasterPaintEngineState *s = state(); + // FALCON: get rid of this line, see drawImage call below. + s->matrix = matrix; + QTransform::TransformationType txop = s->matrix.type(); + + switch (txop) { + + case QTransform::TxNone: + s->flags.int_xform = true; + break; + + case QTransform::TxTranslate: + s->flags.int_xform = qreal(int(s->matrix.dx())) == s->matrix.dx() + && qreal(int(s->matrix.dy())) == s->matrix.dy(); + break; + + case QTransform::TxScale: + s->flags.int_xform = qreal(int(s->matrix.dx())) == s->matrix.dx() + && qreal(int(s->matrix.dy())) == s->matrix.dy() + && qreal(int(s->matrix.m11())) == s->matrix.m11() + && qreal(int(s->matrix.m22())) == s->matrix.m22(); + break; + + default: // shear / perspective... + s->flags.int_xform = false; + break; + } + + extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp + s->flags.tx_noshear = qt_scaleForTransform(s->matrix, &s->txscale); + + ensureOutlineMapper(); + +#ifdef Q_WS_WIN + Q_D(QRasterPaintEngine); + d->isPlain45DegreeRotation = false; + if (txop >= QTransform::TxRotate) { + d->isPlain45DegreeRotation = + (qFuzzyCompare(matrix.m11() + 1, qreal(1)) + && qFuzzyCompare(matrix.m12(), qreal(1)) + && qFuzzyCompare(matrix.m21(), qreal(-1)) + && qFuzzyCompare(matrix.m22() + 1, qreal(1)) + ) + || + (qFuzzyCompare(matrix.m11(), qreal(-1)) + && qFuzzyCompare(matrix.m12() + 1, qreal(1)) + && qFuzzyCompare(matrix.m21() + 1, qreal(1)) + && qFuzzyCompare(matrix.m22(), qreal(-1)) + ) + || + (qFuzzyCompare(matrix.m11() + 1, qreal(1)) + && qFuzzyCompare(matrix.m12(), qreal(-1)) + && qFuzzyCompare(matrix.m21(), qreal(1)) + && qFuzzyCompare(matrix.m22() + 1, qreal(1)) + ) + ; + } +#endif + +} + + + +QRasterPaintEngineState::~QRasterPaintEngineState() +{ + if (flags.has_clip_ownership) + delete clip; +} + + +QRasterPaintEngineState::QRasterPaintEngineState() +{ + stroker = 0; + + fillFlags = 0; + strokeFlags = 0; + pixmapFlags = 0; + + intOpacity = 256; + + txscale = 1.; + + flags.fast_pen = true; + flags.antialiased = false; + flags.bilinear = false; + flags.fast_text = true; + flags.int_xform = true; + flags.tx_noshear = true; + flags.fast_images = true; + + clip = 0; + flags.has_clip_ownership = false; + + dirty = 0; +} + +QRasterPaintEngineState::QRasterPaintEngineState(QRasterPaintEngineState &s) + : QPainterState(s) +{ + stroker = s.stroker; + + lastBrush = s.lastBrush; + brushData = s.brushData; + brushData.tempImage = 0; + + lastPen = s.lastPen; + penData = s.penData; + penData.tempImage = 0; + + fillFlags = s.fillFlags; + strokeFlags = s.strokeFlags; + pixmapFlags = s.pixmapFlags; + + intOpacity = s.intOpacity; + + txscale = s.txscale; + + flag_bits = s.flag_bits; + + clip = s.clip; + flags.has_clip_ownership = false; + + dirty = s.dirty; +} + +/*! + \internal +*/ +QPainterState *QRasterPaintEngine::createState(QPainterState *orig) const +{ + QRasterPaintEngineState *s; + if (!orig) + s = new QRasterPaintEngineState(); + else + s = new QRasterPaintEngineState(*static_cast<QRasterPaintEngineState *>(orig)); + + return s; +} + +/*! + \internal +*/ +void QRasterPaintEngine::setState(QPainterState *s) +{ + Q_D(QRasterPaintEngine); + QPaintEngineEx::setState(s); + d->rasterBuffer->compositionMode = s->composition_mode; +} + +/*! + \fn QRasterPaintEngineState *QRasterPaintEngine::state() + \internal +*/ + +/*! + \fn const QRasterPaintEngineState *QRasterPaintEngine::state() const + \internal +*/ + +/*! + \internal +*/ +void QRasterPaintEngine::penChanged() +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::penChanged():" << state()->pen; +#endif + QRasterPaintEngineState *s = state(); + s->strokeFlags |= DirtyPen; + s->dirty |= DirtyPen; +} + +/*! + \internal +*/ +void QRasterPaintEngine::updatePen(const QPen &pen) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::updatePen():" << s->pen; +#endif + + Qt::PenStyle pen_style = qpen_style(pen); + + s->lastPen = pen; + s->strokeFlags = 0; + + s->penData.clip = d->clip(); + s->penData.setup(pen_style == Qt::NoPen ? QBrush() : pen.brush(), s->intOpacity); + + if (s->strokeFlags & QRasterPaintEngine::DirtyTransform + || pen.brush().transform().type() >= QTransform::TxNone) { + d->updateMatrixData(&s->penData, pen.brush(), s->matrix); + } + + // Slightly ugly handling of an uncommon case... We need to change + // the pen because it is reused in draw_midpoint to decide dashed + // or non-dashed. + if (pen_style == Qt::CustomDashLine && pen.dashPattern().size() == 0) { + pen_style = Qt::SolidLine; + s->lastPen.setStyle(Qt::SolidLine); + } + + d->basicStroker.setJoinStyle(qpen_joinStyle(pen)); + d->basicStroker.setCapStyle(qpen_capStyle(pen)); + d->basicStroker.setMiterLimit(pen.miterLimit()); + + qreal penWidth = qpen_widthf(pen); + if (penWidth == 0) + d->basicStroker.setStrokeWidth(1); + else + d->basicStroker.setStrokeWidth(penWidth); + + if(pen_style == Qt::SolidLine) { + s->stroker = &d->basicStroker; + } else if (pen_style != Qt::NoPen) { + if (!d->dashStroker) + d->dashStroker = new QDashStroker(&d->basicStroker); + if (pen.isCosmetic()) { + d->dashStroker->setClipRect(d->deviceRect); + } else { + // ### I've seen this inverted devrect multiple places now... + QRectF clipRect = s->matrix.inverted().mapRect(QRectF(d->deviceRect)); + d->dashStroker->setClipRect(clipRect); + } + d->dashStroker->setDashPattern(pen.dashPattern()); + d->dashStroker->setDashOffset(pen.dashOffset()); + s->stroker = d->dashStroker; + } else { + s->stroker = 0; + } + + s->flags.fast_pen = pen_style > Qt::NoPen + && s->penData.blend + && !s->flags.antialiased + && (penWidth == 0 || (penWidth <= 1 + && (s->matrix.type() <= QTransform::TxTranslate + || pen.isCosmetic()))); + + ensureState(); // needed because of tx_noshear... + s->flags.non_complex_pen = qpen_capStyle(s->lastPen) <= Qt::SquareCap && s->flags.tx_noshear; + + s->strokeFlags = 0; +} + + + +/*! + \internal +*/ +void QRasterPaintEngine::brushOriginChanged() +{ + QRasterPaintEngineState *s = state(); +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::brushOriginChanged()" << s->brushOrigin; +#endif + + s->fillFlags |= DirtyBrushOrigin; +} + + +/*! + \internal +*/ +void QRasterPaintEngine::brushChanged() +{ + QRasterPaintEngineState *s = state(); +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::brushChanged():" << s->brush; +#endif + s->fillFlags |= DirtyBrush; +} + + + + +/*! + \internal +*/ +void QRasterPaintEngine::updateBrush(const QBrush &brush) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::updateBrush()" << brush; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + // must set clip prior to setup, as setup uses it... + s->brushData.clip = d->clip(); + s->brushData.setup(brush, s->intOpacity); + if (s->fillFlags & DirtyTransform + || brush.transform().type() >= QTransform::TxNone) + d_func()->updateMatrixData(&s->brushData, brush, d->brushMatrix()); + s->lastBrush = brush; + s->fillFlags = 0; +} + +void QRasterPaintEngine::updateOutlineMapper() +{ + Q_D(QRasterPaintEngine); + d->outlineMapper->setMatrix(state()->matrix); +} + +void QRasterPaintEngine::updateState() +{ + QRasterPaintEngineState *s = state(); + + if (s->dirty & DirtyTransform) + updateMatrix(s->matrix); + + if (s->dirty & (DirtyPen|DirtyCompositionMode)) { + const QPainter::CompositionMode mode = s->composition_mode; + s->flags.fast_text = (s->penData.type == QSpanData::Solid) + && (mode == QPainter::CompositionMode_Source + || (mode == QPainter::CompositionMode_SourceOver + && qAlpha(s->penData.solid.color) == 255)); + } + + s->dirty = 0; +} + + +/*! + \internal +*/ +void QRasterPaintEngine::opacityChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::opacityChanged()" << s->opacity; +#endif + + s->fillFlags |= DirtyOpacity; + s->strokeFlags |= DirtyOpacity; + s->pixmapFlags |= DirtyOpacity; + s->intOpacity = (int) (s->opacity * 256); +} + +/*! + \internal +*/ +void QRasterPaintEngine::compositionModeChanged() +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::compositionModeChanged()" << s->composition_mode; +#endif + + s->fillFlags |= DirtyCompositionMode; + s->dirty |= DirtyCompositionMode; + + s->strokeFlags |= DirtyCompositionMode; + d->rasterBuffer->compositionMode = s->composition_mode; + + d->recalculateFastImages(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::renderHintsChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::renderHintsChanged()" << hex << s->renderHints; +#endif + + bool was_aa = s->flags.antialiased; + bool was_bilinear = s->flags.bilinear; + + s->flags.antialiased = bool(s->renderHints & QPainter::Antialiasing); + s->flags.bilinear = bool(s->renderHints & QPainter::SmoothPixmapTransform); + + if (was_aa != s->flags.antialiased) + s->strokeFlags |= DirtyHints; + + if (was_bilinear != s->flags.bilinear) { + s->strokeFlags |= DirtyPen; + s->fillFlags |= DirtyBrush; + } + + Q_D(QRasterPaintEngine); + d->recalculateFastImages(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::transformChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::transformChanged()" << s->matrix; +#endif + + s->fillFlags |= DirtyTransform; + s->strokeFlags |= DirtyTransform; + + s->dirty |= DirtyTransform; + + Q_D(QRasterPaintEngine); + d->recalculateFastImages(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::clipEnabledChanged() +{ + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::clipEnabledChanged()" << s->clipEnabled; +#endif + + if (s->clip) { + s->clip->enabled = s->clipEnabled; + s->fillFlags |= DirtyClipEnabled; + s->strokeFlags |= DirtyClipEnabled; + s->pixmapFlags |= DirtyClipEnabled; + } +} + +#ifdef Q_WS_QWS +void QRasterPaintEnginePrivate::prepare(QCustomRasterPaintDevice *device) +{ + rasterBuffer->prepare(device); +} +#endif + +void QRasterPaintEnginePrivate::drawImage(const QPointF &pt, + const QImage &img, + SrcOverBlendFunc func, + const QRect &clip, + int alpha, + const QRect &sr) +{ + if (!clip.isValid()) + return; + Q_ASSERT(img.depth() >= 8); + + int srcBPL = img.bytesPerLine(); + const uchar *srcBits = img.bits(); + int srcSize = img.depth() >> 3; // This is the part that is incompatible with lower than 8-bit.. + int iw = img.width(); + int ih = img.height(); + + if (!sr.isEmpty()) { + iw = sr.width(); + ih = sr.height(); + // Adjust the image according to the source offset... + srcBits += ((sr.y() * srcBPL) + sr.x() * srcSize); + } + + // adapt the x parameters + int x = qRound(pt.x()); + int cx1 = clip.x(); + int cx2 = clip.x() + clip.width(); + if (x < cx1) { + int d = cx1 - x; + srcBits += srcSize * d; + iw -= d; + x = cx1; + } + if (x + iw > cx2) { + int d = x + iw - cx2; + iw -= d; + } + if (iw < 0) + return; + + // adapt the y paremeters... + int cy1 = clip.y(); + int cy2 = clip.y() + clip.height(); + int y = qRound(pt.y()); + if (y < cy1) { + int d = cy1 - y; + srcBits += srcBPL * d; + ih -= d; + y = cy1; + } + if (y + ih > cy2) { + int d = y + ih - cy2; + ih -= d; + } + if (ih < 0) + return; + + // call the blend function... + int dstSize = rasterBuffer->bytesPerPixel(); + int dstBPL = rasterBuffer->bytesPerLine(); + func(rasterBuffer->buffer() + x * dstSize + y * dstBPL, dstBPL, + srcBits, srcBPL, + iw, ih, + alpha); +} + + +void QRasterPaintEnginePrivate::systemStateChanged() +{ + QRect clipRect(0, 0, + qMin(QT_RASTER_COORD_LIMIT, device->width()), + qMin(QT_RASTER_COORD_LIMIT, device->height())); + + if (!systemClip.isEmpty()) { + QRegion clippedDeviceRgn = systemClip & clipRect; + deviceRect = clippedDeviceRgn.boundingRect(); + delete baseClip; + baseClip = new QClipData(device->height()); + baseClip->setClipRegion(clippedDeviceRgn); + } else { + deviceRect = clipRect; + } +#ifdef QT_DEBUG_DRAW + qDebug() << "systemStateChanged" << this << "deviceRect" << deviceRect << clipRect << systemClip; +#endif + Q_Q(QRasterPaintEngine); + q->state()->strokeFlags |= QPaintEngine::DirtyClipRegion; + q->state()->fillFlags |= QPaintEngine::DirtyClipRegion; + q->state()->pixmapFlags |= QPaintEngine::DirtyClipRegion; +} + +void QRasterPaintEnginePrivate::updateMatrixData(QSpanData *spanData, const QBrush &b, const QTransform &m) +{ + if (b.d->style == Qt::NoBrush || b.d->style == Qt::SolidPattern) + return; + + Q_Q(QRasterPaintEngine); + bool bilinear = q->state()->flags.bilinear; + + if (b.d->transform.type() > QTransform::TxNone) { // FALCON: optimise + spanData->setupMatrix(b.transform() * m, bilinear); + } else { + if (m.type() <= QTransform::TxTranslate) { + // specialize setupMatrix for translation matrices + // to avoid needless matrix inversion + spanData->m11 = 1; + spanData->m12 = 0; + spanData->m13 = 0; + spanData->m21 = 0; + spanData->m22 = 1; + spanData->m23 = 0; + spanData->m33 = 1; + spanData->dx = -m.dx(); + spanData->dy = -m.dy(); + spanData->txop = m.type(); + spanData->bilinear = bilinear; + spanData->fast_matrix = qAbs(m.dx()) < 1e4 && qAbs(m.dy()) < 1e4; + spanData->adjustSpanMethods(); + } else { + spanData->setupMatrix(m, bilinear); + } + } +} + + +/*! + \internal +*/ +void QRasterPaintEngine::clip(const QVectorPath &path, Qt::ClipOperation op) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::clip(): " << path << op; + + if (path.elements()) { + for (int i=0; i<path.elementCount(); ++i) { + qDebug() << " - " << path.elements()[i] + << "(" << path.points()[i*2] << ", " << path.points()[i*2+1] << ")"; + } + } else { + for (int i=0; i<path.elementCount(); ++i) { + qDebug() << " ---- " + << "(" << path.points()[i*2] << ", " << path.points()[i*2+1] << ")"; + } + } +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + const qreal *points = path.points(); + const QPainterPath::ElementType *types = path.elements(); + + // There are some cases that are not supported by clip(QRect) + if (op != Qt::UniteClip && (op != Qt::IntersectClip || !s->clip + || s->clip->hasRectClip || s->clip->hasRegionClip)) { + if (s->matrix.type() <= QTransform::TxTranslate + && ((path.shape() == QVectorPath::RectangleHint) + || (isRect(points, path.elementCount()) + && (!types || (types[0] == QPainterPath::MoveToElement + && types[1] == QPainterPath::LineToElement + && types[2] == QPainterPath::LineToElement + && types[3] == QPainterPath::LineToElement))))) { +#ifdef QT_DEBUG_DRAW + qDebug() << " --- optimizing vector clip to rect clip..."; +#endif + + QRectF r(points[0], points[1], points[4]-points[0], points[5]-points[1]); + clip(r.toRect(), op); + return; + } + } + + if (op == Qt::NoClip) { + if (s->flags.has_clip_ownership) + delete s->clip; + s->clip = 0; + s->flags.has_clip_ownership = false; + + } else { + QClipData *base = d->baseClip; + + // Intersect with current clip when available... + if (op == Qt::IntersectClip && s->clip) + base = s->clip; + + // We always intersect, except when there is nothing to + // intersect with, in which case we simplify the operation to + // a replace... + Qt::ClipOperation isectOp = Qt::IntersectClip; + if (base == 0) + isectOp = Qt::ReplaceClip; + + QClipData *newClip = new QClipData(d->rasterBuffer->height()); + newClip->initialize(); + ClipData clipData = { base, newClip, isectOp }; + ensureOutlineMapper(); + d->rasterize(d->outlineMapper->convertPath(path), qt_span_clip, &clipData, 0); + + newClip->fixup(); + + if (op == Qt::UniteClip) { + // merge clips + QClipData *result = new QClipData(d->rasterBuffer->height()); + QClipData *current = s->clip ? s->clip : new QClipData(d->rasterBuffer->height()); + qt_merge_clip(current, newClip, result); + result->fixup(); + delete newClip; + if (!s->clip) + delete current; + newClip = result; + } + + if (s->flags.has_clip_ownership) + delete s->clip; + + s->clip = newClip; + s->flags.has_clip_ownership = true; + } + + s->fillFlags |= DirtyClipPath; + s->strokeFlags |= DirtyClipPath; + s->pixmapFlags |= DirtyClipPath; + + d->solid_color_filler.clip = d->clip(); + d->solid_color_filler.adjustSpanMethods(); +} + + + +/*! + \internal +*/ +void QRasterPaintEngine::clip(const QRect &rect, Qt::ClipOperation op) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::clip(): " << rect << op; +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (op == Qt::NoClip) { + if (s->flags.has_clip_ownership) + delete s->clip; + s->clip = d->baseClip; + s->flags.has_clip_ownership = false; + + } else if (op == Qt::UniteClip || s->matrix.type() > QTransform::TxScale) { + QPaintEngineEx::clip(rect, op); + return; + + } else if (op == Qt::ReplaceClip || s->clip == 0) { + + // No current clip, hence we intersect with sysclip and be + // done with it... + QRect clipRect = s->matrix.mapRect(rect) & d->deviceRect; + QRegion clipRegion = systemClip(); + QClipData *clip = new QClipData(d->rasterBuffer->height()); + + if (clipRegion.isEmpty()) + clip->setClipRect(clipRect); + else + clip->setClipRegion(clipRegion & clipRect); + + if (s->flags.has_clip_ownership) + delete s->clip; + + s->clip = clip; + s->flags.has_clip_ownership = true; + + } else { // intersect clip with current clip + QClipData *base = s->clip; + + Q_ASSERT(base); + if (base->hasRectClip || base->hasRegionClip) { + QRect clipRect = s->matrix.mapRect(rect) & d->deviceRect; + if (!s->flags.has_clip_ownership) { + s->clip = new QClipData(d->rasterBuffer->height()); + s->flags.has_clip_ownership = true; + } + if (base->hasRectClip) + s->clip->setClipRect(base->clipRect & clipRect); + else + s->clip->setClipRegion(base->clipRegion & clipRect); + } else { + QPaintEngineEx::clip(rect, op); + return; + } + } + + s->brushData.clip = d->clip(); + s->penData.clip = d->clip(); + + s->fillFlags |= DirtyClipPath; + s->strokeFlags |= DirtyClipPath; + s->pixmapFlags |= DirtyClipPath; + + d->solid_color_filler.clip = d->clip(); + d->solid_color_filler.adjustSpanMethods(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::clip(const QRegion ®ion, Qt::ClipOperation op) +{ + QPaintEngineEx::clip(region, op); +} + +/*! + \internal +*/ +void QRasterPaintEngine::clip(const QPainterPath &path, Qt::ClipOperation op) +{ + QPaintEngineEx::clip(path, op); +} + +/*! + \internal +*/ +void QRasterPaintEngine::fillPath(const QPainterPath &path, QSpanData *fillData) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " --- fillPath, bounds=" << path.boundingRect(); +#endif + + if (!fillData->blend) + return; + + Q_D(QRasterPaintEngine); + + const QRectF controlPointRect = path.controlPointRect(); + + QRasterPaintEngineState *s = state(); + const QRect deviceRect = s->matrix.mapRect(controlPointRect).toRect(); + ProcessSpans blend = d->getBrushFunc(deviceRect, fillData); + const bool do_clip = (deviceRect.left() < -QT_RASTER_COORD_LIMIT + || deviceRect.right() > QT_RASTER_COORD_LIMIT + || deviceRect.top() < -QT_RASTER_COORD_LIMIT + || deviceRect.bottom() > QT_RASTER_COORD_LIMIT); + + if (!s->flags.antialiased && !do_clip) { + d->initializeRasterizer(fillData); + d->rasterizer->rasterize(path * s->matrix, path.fillRule()); + return; + } + + ensureOutlineMapper(); + d->rasterize(d->outlineMapper->convertPath(path), blend, fillData, d->rasterBuffer); +} + +static void fillRect_normalized(const QRect &r, QSpanData *data, + QRasterPaintEnginePrivate *pe) +{ + int x1, x2, y1, y2; + + bool rectClipped = false; + + if (data->clip) { + x1 = qMax(r.x(), data->clip->xmin); + x2 = qMin(r.x() + r.width(), data->clip->xmax); + y1 = qMax(r.y(), data->clip->ymin); + y2 = qMin(r.y() + r.height(), data->clip->ymax); + rectClipped = data->clip->hasRectClip; + + } else if (pe) { + x1 = qMax(r.x(), pe->deviceRect.x()); + x2 = qMin(r.x() + r.width(), pe->deviceRect.x() + pe->deviceRect.width()); + y1 = qMax(r.y(), pe->deviceRect.y()); + y2 = qMin(r.y() + r.height(), pe->deviceRect.y() + pe->deviceRect.height()); + } else { + x1 = qMax(r.x(), 0); + x2 = qMin(r.x() + r.width(), data->rasterBuffer->width()); + y1 = qMax(r.y(), 0); + y2 = qMin(r.y() + r.height(), data->rasterBuffer->height()); + } + + if (x2 <= x1 || y2 <= y1) + return; + + const int width = x2 - x1; + const int height = y2 - y1; + + bool isUnclipped = rectClipped + || (pe && pe->isUnclipped_normalized(QRect(x1, y1, width, height))); + + if (pe && isUnclipped) { + const QPainter::CompositionMode mode = pe->rasterBuffer->compositionMode; + + if (data->fillRect && (mode == QPainter::CompositionMode_Source + || (mode == QPainter::CompositionMode_SourceOver + && qAlpha(data->solid.color) == 255))) + { + data->fillRect(data->rasterBuffer, x1, y1, width, height, + data->solid.color); + return; + } + } + + ProcessSpans blend = isUnclipped ? data->unclipped_blend : data->blend; + + const int nspans = 256; + QT_FT_Span spans[nspans]; + + Q_ASSERT(data->blend); + int y = y1; + while (y < y2) { + int n = qMin(nspans, y2 - y); + int i = 0; + while (i < n) { + spans[i].x = x1; + spans[i].len = width; + spans[i].y = y + i; + spans[i].coverage = 255; + ++i; + } + + blend(n, spans, data); + y += n; + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawRects(const QRect *rects, int rectCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawRect(), rectCount=%d", rectCount); +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + // Fill + ensureBrush(); + if (s->brushData.blend) { + if (!s->flags.antialiased && s->matrix.type() <= QTransform::TxTranslate) { + const QRect *r = rects; + const QRect *lastRect = rects + rectCount; + + int offset_x = int(s->matrix.dx()); + int offset_y = int(s->matrix.dy()); + while (r < lastRect) { + QRect rect = r->normalized(); + QRect rr = rect.translated(offset_x, offset_y); + fillRect_normalized(rr, &s->brushData, d); + ++r; + } + } else { + QRectVectorPath path; + for (int i=0; i<rectCount; ++i) { + path.set(rects[i]); + fill(path, s->brush); + } + } + } + + ensurePen(); + if (s->penData.blend) { + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) { + const QRect *r = rects; + const QRect *lastRect = rects + rectCount; + while (r < lastRect) { + int left = r->x(); + int right = r->x() + r->width(); + int top = r->y(); + int bottom = r->y() + r->height(); + +#ifdef Q_WS_MAC + int pts[] = { top, left, + top, right, + bottom, right, + bottom, left }; +#else + int pts[] = { left, top, + right, top, + right, bottom, + left, bottom }; +#endif + + strokePolygonCosmetic((QPoint *) pts, 4, WindingMode); + ++r; + } + } else { + QRectVectorPath path; + for (int i = 0; i < rectCount; ++i) { + path.set(rects[i]); + stroke(path, s->pen); + } + } + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawRects(const QRectF *rects, int rectCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawRect(), rectCount=%d", rectCount); +#endif +#ifdef QT_FAST_SPANS + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensureState(); + + if (s->flags.tx_noshear) { + ensureBrush(); + if (s->brushData.blend) { + d->initializeRasterizer(&s->brushData); + for (int i = 0; i < rectCount; ++i) { + const QRectF &rect = rects[i].normalized(); + if (rects[i].isEmpty()) + continue; + const QPointF a = s->matrix.map((rect.topLeft() + rect.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((rect.topRight() + rect.bottomRight()) * 0.5f); + d->rasterizer->rasterizeLine(a, b, rect.height() / rect.width()); + } + } + + ensurePen(); + if (s->penData.blend) { + qreal width = s->pen.isCosmetic() + ? (s->lastPen.widthF() == 0 ? 1 : s->lastPen.widthF()) + : s->lastPen.widthF() * s->txscale; + + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) { + for (int i = 0; i < rectCount; ++i) { + const QRectF &r = rects[i]; + qreal left = r.x(); + qreal right = r.x() + r.width(); + qreal top = r.y(); + qreal bottom = r.y() + r.height(); + qreal pts[] = { left, top, + right, top, + right, bottom, + left, bottom }; + strokePolygonCosmetic((QPointF *) pts, 4, WindingMode); + } + } else if (width <= 1 && qpen_style(s->lastPen) == Qt::SolidLine) { + d->initializeRasterizer(&s->penData); + + for (int i = 0; i < rectCount; ++i) { + const QRectF &rect = rects[i].normalized(); + if (rect.isEmpty()) { + qreal pts[] = { rect.left(), rect.top(), rect.right(), rect.bottom() }; + QVectorPath vp(pts, 2, 0, QVectorPath::LinesHint); + QPaintEngineEx::stroke(vp, s->lastPen); + } else { + const QPointF tl = s->matrix.map(rect.topLeft()); + const QPointF tr = s->matrix.map(rect.topRight()); + const QPointF bl = s->matrix.map(rect.bottomLeft()); + const QPointF br = s->matrix.map(rect.bottomRight()); + const qreal w = width / (rect.width() * s->txscale); + const qreal h = width / (rect.height() * s->txscale); + d->rasterizer->rasterizeLine(tl, tr, w); // top + d->rasterizer->rasterizeLine(bl, br, w); // bottom + d->rasterizer->rasterizeLine(bl, tl, h); // left + d->rasterizer->rasterizeLine(br, tr, h); // right + } + } + } else { + for (int i = 0; i < rectCount; ++i) { + const QRectF &r = rects[i]; + qreal left = r.x(); + qreal right = r.x() + r.width(); + qreal top = r.y(); + qreal bottom = r.y() + r.height(); + qreal pts[] = { left, top, + right, top, + right, bottom, + left, bottom, + left, top }; + QVectorPath vp(pts, 5, 0, QVectorPath::RectangleHint); + QPaintEngineEx::stroke(vp, s->lastPen); + } + } + } + + return; + } +#endif // QT_FAST_SPANS + QPaintEngineEx::drawRects(rects, rectCount); +} + +void QRasterPaintEnginePrivate::strokeProjective(const QPainterPath &path) +{ + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + const QPen &pen = s->lastPen; + QPainterPathStroker pathStroker; + pathStroker.setWidth(pen.width() == 0 ? qreal(1) : pen.width()); + pathStroker.setCapStyle(pen.capStyle()); + pathStroker.setJoinStyle(pen.joinStyle()); + pathStroker.setMiterLimit(pen.miterLimit()); + pathStroker.setDashOffset(pen.dashOffset()); + + if (qpen_style(pen) == Qt::CustomDashLine) + pathStroker.setDashPattern(pen.dashPattern()); + else + pathStroker.setDashPattern(qpen_style(pen)); + + outlineMapper->setMatrix(QTransform()); + const QPainterPath stroke = pen.isCosmetic() + ? pathStroker.createStroke(s->matrix.map(path)) + : s->matrix.map(pathStroker.createStroke(path)); + + rasterize(outlineMapper->convertPath(stroke), s->penData.blend, &s->penData, rasterBuffer); + outlinemapper_xform_dirty = true; +} + + + +/*! + \internal +*/ +void QRasterPaintEngine::stroke(const QVectorPath &path, const QPen &pen) +{ + QRasterPaintEngineState *s = state(); + ensurePen(pen); + if (!s->penData.blend) + return; + + if (s->flags.fast_pen && path.shape() <= QVectorPath::NonCurvedShapeHint && s->lastPen.brush().isOpaque()) { + strokePolygonCosmetic((QPointF *) path.points(), path.elementCount(), + path.hasImplicitClose() + ? WindingMode + : PolylineMode); + + } else if (s->flags.non_complex_pen && path.shape() == QVectorPath::LinesHint) { + qreal width = s->lastPen.isCosmetic() + ? (qpen_widthf(s->lastPen) == 0 ? 1 : qpen_widthf(s->lastPen)) + : qpen_widthf(s->lastPen) * s->txscale; + int dashIndex = 0; + qreal dashOffset = s->lastPen.dashOffset(); + bool inDash = true; + qreal patternLength = 0; + const QVector<qreal> pattern = s->lastPen.dashPattern(); + for (int i = 0; i < pattern.size(); ++i) + patternLength += pattern.at(i); + + if (patternLength > 0) { + int n = qFloor(dashOffset / patternLength); + dashOffset -= n * patternLength; + while (dashOffset > pattern.at(dashIndex)) { + dashOffset -= pattern.at(dashIndex); + dashIndex = (dashIndex + 1) % pattern.size(); + inDash = !inDash; + } + } + + Q_D(QRasterPaintEngine); + d->initializeRasterizer(&s->penData); + int lineCount = path.elementCount() / 2; + const QLineF *lines = reinterpret_cast<const QLineF *>(path.points()); + + for (int i = 0; i < lineCount; ++i) { + if (lines[i].p1() == lines[i].p2()) { + if (s->lastPen.capStyle() != Qt::FlatCap) { + QPointF p = lines[i].p1(); + QLineF line = s->matrix.map(QLineF(QPointF(p.x() - width*0.5, p.y()), + QPointF(p.x() + width*0.5, p.y()))); + d->rasterizer->rasterizeLine(line.p1(), line.p2(), 1); + } + continue; + } + + const QLineF line = s->matrix.map(lines[i]); + if (qpen_style(s->lastPen) == Qt::SolidLine) { + d->rasterizer->rasterizeLine(line.p1(), line.p2(), + width / line.length(), + s->lastPen.capStyle() == Qt::SquareCap); + } else { + d->rasterizeLine_dashed(line, width, + &dashIndex, &dashOffset, &inDash); + } + } + } + else + QPaintEngineEx::stroke(path, pen); +} + +static inline QRect toNormalizedFillRect(const QRectF &rect) +{ + const int x1 = qFloor(rect.x() + aliasedCoordinateDelta); + const int y1 = qFloor(rect.y() + aliasedCoordinateDelta); + const int x2 = qFloor(rect.right() + aliasedCoordinateDelta); + const int y2 = qFloor(rect.bottom() + aliasedCoordinateDelta); + + return QRect(x1, y1, x2 - x1, y2 - y1).normalized(); +} + +/*! + \internal +*/ +void QRasterPaintEngine::fill(const QVectorPath &path, const QBrush &brush) +{ + if (path.isEmpty()) + return; +#ifdef QT_DEBUG_DRAW + QRealRect vectorPathBounds = path.controlPointRect(); + QRectF rf(vectorPathBounds.x1, vectorPathBounds.y1, + vectorPathBounds.x2 - vectorPathBounds.x1, vectorPathBounds.y2 - vectorPathBounds.y1); + qDebug() << "QRasterPaintEngine::fill(): " + << "size=" << path.elementCount() + << ", hints=" << hex << path.hints() + << rf << brush; +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensureBrush(brush); + if (!s->brushData.blend) + return; + + if (path.shape() == QVectorPath::RectangleHint) { + if (!s->flags.antialiased && s->matrix.type() <= QTransform::TxScale) { + const qreal *p = path.points(); + QPointF tl = QPointF(p[0], p[1]) * s->matrix; + QPointF br = QPointF(p[4], p[5]) * s->matrix; + fillRect_normalized(toNormalizedFillRect(QRectF(tl, br)), &s->brushData, d); + return; + } + ensureState(); + if (s->flags.tx_noshear) { + d->initializeRasterizer(&s->brushData); + // ### Is normalizing really nessesary here? + const qreal *p = path.points(); + QRectF r = QRectF(p[0], p[1], p[2] - p[0], p[7] - p[1]).normalized(); + if (!r.isEmpty()) { + const QPointF a = s->matrix.map((r.topLeft() + r.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((r.topRight() + r.bottomRight()) * 0.5f); + d->rasterizer->rasterizeLine(a, b, r.height() / r.width()); + } + return; + } + } + + if (path.shape() == QVectorPath::EllipseHint) { + if (!s->flags.antialiased && s->matrix.type() <= QTransform::TxScale) { + const qreal *p = path.points(); + QPointF tl = QPointF(p[0], p[1]) * s->matrix; + QPointF br = QPointF(p[4], p[5]) * s->matrix; + QRectF r = s->matrix.mapRect(QRectF(tl, br)); + + ProcessSpans penBlend = d->getPenFunc(r, &s->penData); + ProcessSpans brushBlend = d->getBrushFunc(r, &s->brushData); + const QRect brect = QRect(int(r.x()), int(r.y()), + int_dim(r.x(), r.width()), + int_dim(r.y(), r.height())); + if (brect == r) { + drawEllipse_midpoint_i(brect, d->deviceRect, penBlend, brushBlend, + &s->penData, &s->brushData); + return; + } + } + } + + // ### Optimize for non transformed ellipses and rectangles... + QRealRect r = path.controlPointRect(); + QRectF cpRect(r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1); + const QRect deviceRect = s->matrix.mapRect(cpRect).toRect(); + ProcessSpans blend = d->getBrushFunc(deviceRect, &s->brushData); + + // ### Falcon +// const bool do_clip = (deviceRect.left() < -QT_RASTER_COORD_LIMIT +// || deviceRect.right() > QT_RASTER_COORD_LIMIT +// || deviceRect.top() < -QT_RASTER_COORD_LIMIT +// || deviceRect.bottom() > QT_RASTER_COORD_LIMIT); + + // ### Falonc: implement.... +// if (!s->flags.antialiased && !do_clip) { +// d->initializeRasterizer(&s->brushData); +// d->rasterizer->rasterize(path * d->matrix, path.fillRule()); +// return; +// } + + ensureOutlineMapper(); + d->rasterize(d->outlineMapper->convertPath(path), blend, &s->brushData, d->rasterBuffer); +} + +void QRasterPaintEngine::fillRect(const QRectF &r, QSpanData *data) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (!s->flags.antialiased) { + uint txop = s->matrix.type(); + if (txop == QTransform::TxNone) { + fillRect_normalized(toNormalizedFillRect(r), data, d); + return; + } else if (txop == QTransform::TxTranslate) { + const QRect rr = toNormalizedFillRect(r.translated(s->matrix.dx(), s->matrix.dy())); + fillRect_normalized(rr, data, d); + return; + } else if (txop == QTransform::TxScale) { + const QRect rr = toNormalizedFillRect(s->matrix.mapRect(r)); + fillRect_normalized(rr, data, d); + return; + } + } + ensureState(); + if (s->flags.tx_noshear) { + d->initializeRasterizer(data); + QRectF nr = r.normalized(); + if (!nr.isEmpty()) { + const QPointF a = s->matrix.map((nr.topLeft() + nr.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((nr.topRight() + nr.bottomRight()) * 0.5f); + d->rasterizer->rasterizeLine(a, b, nr.height() / nr.width()); + } + return; + } + + QPainterPath path; + path.addRect(r); + ensureOutlineMapper(); + fillPath(path, data); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::fillRect(const QRectF &r, const QBrush &brush) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::fillRecct(): " << r << brush; +#endif + QRasterPaintEngineState *s = state(); + + ensureBrush(brush); + if (!s->brushData.blend) + return; + + fillRect(r, &s->brushData); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::fillRect(const QRectF &r, const QColor &color) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << "QRasterPaintEngine::fillRect(): " << r << color; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + d->solid_color_filler.solid.color = PREMUL(ARGB_COMBINE_ALPHA(color.rgba(), s->intOpacity)); + d->solid_color_filler.clip = d->clip(); + d->solid_color_filler.adjustSpanMethods(); + + fillRect(r, &d->solid_color_filler); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPath(const QPainterPath &path) +{ +#ifdef QT_DEBUG_DRAW + QRectF bounds = path.boundingRect(); + qDebug(" - QRasterPaintEngine::drawPath(), [%.2f, %.2f, %.2f, %.2f]", + bounds.x(), bounds.y(), bounds.width(), bounds.height()); +#endif + + if (path.isEmpty()) + return; + + // Filling.., + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensureBrush(); + if (s->brushData.blend) { + ensureOutlineMapper(); + fillPath(path, &s->brushData); + } + + // Stroking... + ensurePen(); + if (!s->penData.blend) + return; + { + if (s->matrix.type() >= QTransform::TxProject) { + d->strokeProjective(path); + } else { + Q_ASSERT(s->stroker); + d->outlineMapper->beginOutline(Qt::WindingFill); + extern bool qt_scaleForTransform(const QTransform &transform, qreal *scale); // qtransform.cpp + qreal txscale = 1; + if (s->pen.isCosmetic() || (qt_scaleForTransform(s->matrix, &txscale) && txscale != 1)) { + const qreal strokeWidth = d->basicStroker.strokeWidth(); + const QRectF clipRect = d->dashStroker ? d->dashStroker->clipRect() : QRectF(); + if (d->dashStroker) + d->dashStroker->setClipRect(d->deviceRect); + d->basicStroker.setStrokeWidth(strokeWidth * txscale); + d->outlineMapper->setMatrix(QTransform()); + s->stroker->strokePath(path, d->outlineMapper, s->matrix); + d->outlinemapper_xform_dirty = true; + d->basicStroker.setStrokeWidth(strokeWidth); + if (d->dashStroker) + d->dashStroker->setClipRect(clipRect); + } else { + ensureOutlineMapper(); + s->stroker->strokePath(path, d->outlineMapper, QTransform()); + } + d->outlineMapper->endOutline(); + + ProcessSpans blend = d->getPenFunc(d->outlineMapper->controlPointRect, + &s->penData); + d->rasterize(d->outlineMapper->outline(), blend, &s->penData, d->rasterBuffer); + } + } + +} + +static inline bool isAbove(const QPointF *a, const QPointF *b) +{ + return a->y() < b->y(); +} + +static bool splitPolygon(const QPointF *points, int pointCount, QVector<QPointF> *upper, QVector<QPointF> *lower) +{ + Q_ASSERT(upper); + Q_ASSERT(lower); + + Q_ASSERT(pointCount >= 2); + + QVector<const QPointF *> sorted; + sorted.reserve(pointCount); + + upper->reserve(pointCount * 3 / 4); + lower->reserve(pointCount * 3 / 4); + + for (int i = 0; i < pointCount; ++i) + sorted << points + i; + + qSort(sorted.begin(), sorted.end(), isAbove); + + qreal splitY = sorted.at(sorted.size() / 2)->y(); + + const QPointF *end = points + pointCount; + const QPointF *last = end - 1; + + QVector<QPointF> *bin[2] = { upper, lower }; + + for (const QPointF *p = points; p < end; ++p) { + int side = p->y() < splitY; + int lastSide = last->y() < splitY; + + if (side != lastSide) { + if (qFuzzyCompare(p->y(), splitY)) { + bin[!side]->append(*p); + } else if (qFuzzyCompare(last->y(), splitY)) { + bin[side]->append(*last); + } else { + QPointF delta = *p - *last; + QPointF intersection(p->x() + delta.x() * (splitY - p->y()) / delta.y(), splitY); + + bin[0]->append(intersection); + bin[1]->append(intersection); + } + } + + bin[side]->append(*p); + + last = p; + } + + // give up if we couldn't reduce the point count + return upper->size() < pointCount && lower->size() < pointCount; +} + +/*! + \internal + */ +void QRasterPaintEngine::fillPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + const int maxPoints = 0xffff; + + // max amount of points that raster engine can reliably handle + if (pointCount > maxPoints) { + QVector<QPointF> upper, lower; + + if (splitPolygon(points, pointCount, &upper, &lower)) { + fillPolygon(upper.constData(), upper.size(), mode); + fillPolygon(lower.constData(), lower.size(), mode); + } else + qWarning("Polygon too complex for filling."); + + return; + } + + // Compose polygon fill.., + QVectorPath vp((qreal *) points, pointCount, 0, QVectorPath::polygonFlags(mode)); + ensureOutlineMapper(); + QT_FT_Outline *outline = d->outlineMapper->convertPath(vp); + + // scanconvert. + ProcessSpans brushBlend = d->getBrushFunc(d->outlineMapper->controlPointRect, + &s->brushData); + d->rasterize(outline, brushBlend, &s->brushData, d->rasterBuffer); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPolygon(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawPolygon(F), pointCount=%d", pointCount); + for (int i=0; i<pointCount; ++i) + qDebug() << " - " << points[i]; +#endif + Q_ASSERT(pointCount >= 2); + + if (mode != PolylineMode && isRect((qreal *) points, pointCount)) { + QRectF r(points[0], points[2]); + drawRects(&r, 1); + return; + } + + ensurePen(); + ensureBrush(); + if (mode != PolylineMode) { + // Do the fill... + if (s->brushData.blend) { + d->outlineMapper->setCoordinateRounding(s->penData.blend && s->flags.fast_pen && s->lastPen.brush().isOpaque()); + fillPolygon(points, pointCount, mode); + d->outlineMapper->setCoordinateRounding(false); + } + } + + // Do the outline... + if (s->penData.blend) { + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) + strokePolygonCosmetic(points, pointCount, mode); + else { + QVectorPath vp((qreal *) points, pointCount, 0, QVectorPath::polygonFlags(mode)); + QPaintEngineEx::stroke(vp, s->lastPen); + } + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPolygon(const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + qDebug(" - QRasterPaintEngine::drawPolygon(I), pointCount=%d", pointCount); + for (int i=0; i<pointCount; ++i) + qDebug() << " - " << points[i]; +#endif + Q_ASSERT(pointCount >= 2); + if (mode != PolylineMode && isRect((int *) points, pointCount)) { + QRect r(points[0].x(), + points[0].y(), + points[2].x() - points[0].x(), + points[2].y() - points[0].y()); + drawRects(&r, 1); + return; + } + + ensureState(); + ensurePen(); + if (!(s->flags.int_xform && s->flags.fast_pen && (!s->penData.blend || s->pen.brush().isOpaque()))) { + // this calls the float version + QPaintEngineEx::drawPolygon(points, pointCount, mode); + return; + } + + // Do the fill + if (mode != PolylineMode) { + ensureBrush(); + if (s->brushData.blend) { + // Compose polygon fill.., + ensureOutlineMapper(); + d->outlineMapper->setCoordinateRounding(s->penData.blend != 0); + d->outlineMapper->beginOutline(mode == WindingMode ? Qt::WindingFill : Qt::OddEvenFill); + d->outlineMapper->moveTo(*points); + const QPoint *p = points; + const QPoint *ep = points + pointCount - 1; + do { + d->outlineMapper->lineTo(*(++p)); + } while (p < ep); + d->outlineMapper->endOutline(); + + // scanconvert. + ProcessSpans brushBlend = d->getBrushFunc(d->outlineMapper->controlPointRect, + &s->brushData); + d->rasterize(d->outlineMapper->outline(), brushBlend, &s->brushData, d->rasterBuffer); + d->outlineMapper->setCoordinateRounding(false); + } + } + + // Do the outline... + if (s->penData.blend) { + if (s->flags.fast_pen && s->lastPen.brush().isOpaque()) + strokePolygonCosmetic(points, pointCount, mode); + else { + int count = pointCount * 2; + QVarLengthArray<qreal> fpoints(count); +#ifdef Q_WS_MAC + for (int i=0; i<count; i+=2) { + fpoints[i] = ((int *) points)[i+1]; + fpoints[i+1] = ((int *) points)[i]; + } +#else + for (int i=0; i<count; ++i) + fpoints[i] = ((int *) points)[i]; +#endif + QVectorPath vp((qreal *) fpoints.data(), pointCount, 0, QVectorPath::polygonFlags(mode)); + QPaintEngineEx::stroke(vp, s->lastPen); + } + } +} + +/*! + \internal +*/ +void QRasterPaintEngine::strokePolygonCosmetic(const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + Q_ASSERT(s->penData.blend); + Q_ASSERT(s->flags.fast_pen); + + bool needs_closing = mode != PolylineMode && points[0] != points[pointCount-1]; + + // Use fast path for 0 width / trivial pens. + QIntRect devRect; + devRect.set(d->deviceRect); + + LineDrawMode mode_for_last = (s->lastPen.capStyle() != Qt::FlatCap + ? LineDrawIncludeLastPixel + : LineDrawNormal); + int dashOffset = int(s->lastPen.dashOffset()); + + const QPointF offs(aliasedCoordinateDelta, aliasedCoordinateDelta); + + // Draw all the line segments. + for (int i=1; i<pointCount; ++i) { + + QPointF lp1 = points[i-1] * s->matrix + offs; + QPointF lp2 = points[i] * s->matrix + offs; + + const QRectF brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) { + drawLine_midpoint_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect); + } else { + drawLine_midpoint_dashed_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + &s->lastPen, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } + + // Polygons are implicitly closed. + if (needs_closing) { + QPointF lp1 = points[pointCount-1] * s->matrix + offs; + QPointF lp2 = points[0] * s->matrix + offs; + + const QRectF brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) { + drawLine_midpoint_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + penBlend, &s->penData, + LineDrawIncludeLastPixel, + devRect); + } else { + drawLine_midpoint_dashed_i(qFloor(lp1.x()), qFloor(lp1.y()), + qFloor(lp2.x()), qFloor(lp2.y()), + &s->lastPen, + penBlend, &s->penData, + LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } + +} + +/*! + \internal +*/ +void QRasterPaintEngine::strokePolygonCosmetic(const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + // We assert here because this function is called from drawRects + // and drawPolygon and they already do ensurePen(), so we skip that + // here to avoid duplicate checks.. + Q_ASSERT(s->penData.blend); + + bool needs_closing = mode != PolylineMode && points[0] != points[pointCount-1]; + + QIntRect devRect; + devRect.set(d->deviceRect); + + LineDrawMode mode_for_last = (s->lastPen.capStyle() != Qt::FlatCap + ? LineDrawIncludeLastPixel + : LineDrawNormal); + + int m11 = int(s->matrix.m11()); + int m22 = int(s->matrix.m22()); + int dx = int(s->matrix.dx()); + int dy = int(s->matrix.dy()); + int m13 = int(s->matrix.m13()); + int m23 = int(s->matrix.m23()); + bool affine = !m13 && !m23; + + int dashOffset = int(s->lastPen.dashOffset()); + + if (affine) { + // Draw all the line segments. + for (int i=1; i<pointCount; ++i) { + const QPoint lp1 = points[i-1] * s->matrix; + const QPoint lp2 = points[i] * s->matrix; + const QRect brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + &s->lastPen, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect, &dashOffset); + + } + + // Polygons are implicitly closed. + if (needs_closing) { + const QPoint lp1 = points[pointCount - 1] * s->matrix; + const QPoint lp2 = points[0] * s->matrix; + const QRect brect(lp1, lp2); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(lp1.x(), lp1.y(), + lp2.x(), lp2.y(), + &s->lastPen, + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } else { + // Draw all the line segments. + for (int i=1; i<pointCount; ++i) { + int x1 = points[i-1].x() * m11 + dx; + int y1 = points[i-1].y() * m22 + dy; + qreal w = m13*points[i-1].x() + m23*points[i-1].y() + 1.; + w = 1/w; + x1 = int(x1*w); + y1 = int(y1*w); + int x2 = points[i].x() * m11 + dx; + int y2 = points[i].y() * m22 + dy; + w = m13*points[i].x() + m23*points[i].y() + 1.; + w = 1/w; + x2 = int(x2*w); + y2 = int(y2*w); + + const QRect brect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(x1, y1, x2, y2, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(x1, y1, x2, y2, + &s->lastPen, + penBlend, &s->penData, + i == pointCount - 1 ? mode_for_last : LineDrawIncludeLastPixel, + devRect, &dashOffset); + + } + + int x1 = points[pointCount-1].x() * m11 + dx; + int y1 = points[pointCount-1].y() * m22 + dy; + qreal w = m13*points[pointCount-1].x() + m23*points[pointCount-1].y() + 1.; + w = 1/w; + x1 = int(x1*w); + y1 = int(y1*w); + int x2 = points[0].x() * m11 + dx; + int y2 = points[0].y() * m22 + dy; + w = m13*points[0].x() + m23*points[0].y() + 1.; + w = 1/w; + x2 = int(x2 * w); + y2 = int(y2 * w); + // Polygons are implicitly closed. + + if (needs_closing) { + const QRect brect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(x1, y1, x2, y2, + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect); + else + drawLine_midpoint_dashed_i(x1, y1, x2, y2, + &s->lastPen, + penBlend, &s->penData, LineDrawIncludeLastPixel, + devRect, &dashOffset); + } + } +} + +#define IMAGE_FROM_PIXMAP(pixmap) \ + pixmap.data->classId() == QPixmapData::RasterClass \ + ? ((QRasterPixmapData *) pixmap.data)->image \ + : pixmap.toImage() + +/*! + \internal +*/ +void QRasterPaintEngine::drawPixmap(const QPointF &pos, const QPixmap &pixmap) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawPixmap(), pos=" << pos << " pixmap=" << pixmap.size() << "depth=" << pixmap.depth(); +#endif + if (pixmap.depth() == 1) { + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + if (s->matrix.type() <= QTransform::TxTranslate) { + drawBitmap(pos + QPointF(s->matrix.dx(), s->matrix.dy()), pixmap, &s->penData); + } else { + drawImage(pos, d->rasterBuffer->colorizeBitmap(IMAGE_FROM_PIXMAP(pixmap), s->pen.color())); + } + } else { + QRasterPaintEngine::drawImage(pos, IMAGE_FROM_PIXMAP(pixmap)); + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPixmap(const QRectF &r, const QPixmap &pixmap, const QRectF &sr) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawPixmap(), r=" << r << " sr=" << sr << " pixmap=" << pixmap.size() << "depth=" << pixmap.depth(); +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (pixmap.depth() == 1) { + if (s->matrix.type() <= QTransform::TxTranslate + && r.size() == sr.size() + && r.size() == pixmap.size()) { + ensurePen(); + drawBitmap(r.topLeft() + QPointF(s->matrix.dx(), s->matrix.dy()), pixmap, &s->penData); + return; + } else { + drawImage(r, d->rasterBuffer->colorizeBitmap(IMAGE_FROM_PIXMAP(pixmap), + s->pen.color()), sr); + } + } else { + drawImage(r, IMAGE_FROM_PIXMAP(pixmap), sr); + } +} + +// assumes that rect has positive width and height +static inline const QRect toRect_normalized(const QRectF &rect) +{ + const int x = qRound(rect.x()); + const int y = qRound(rect.y()); + const int w = int(rect.width() + qreal(0.5)); + const int h = int(rect.height() + qreal(0.5)); + + return QRect(x, y, w, h); +} + +static inline int fast_ceil_positive(const qreal &v) +{ + const int iv = int(v); + if (v - iv == 0) + return iv; + else + return iv + 1; +} + +static inline const QRect toAlignedRect_positive(const QRectF &rect) +{ + const int xmin = int(rect.x()); + const int xmax = int(fast_ceil_positive(rect.right())); + const int ymin = int(rect.y()); + const int ymax = int(fast_ceil_positive(rect.bottom())); + return QRect(xmin, ymin, xmax - xmin, ymax - ymin); +} + +/*! + \internal +*/ +void QRasterPaintEngine::drawImage(const QPointF &p, const QImage &img) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawImage(), p=" << p << " image=" << img.size() << "depth=" << img.depth(); +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (s->matrix.type() > QTransform::TxTranslate) { + drawImage(QRectF(p.x(), p.y(), img.width(), img.height()), + img, + QRectF(0, 0, img.width(), img.height())); + } else { + + const QClipData *clip = d->clip(); + QPointF pt(p.x() + s->matrix.dx(), p.y() + s->matrix.dy()); + + // ### TODO: remove this eventually... + static bool NO_BLEND_FUNC = !qgetenv("QT_NO_BLEND_FUNCTIONS").isNull(); + + if (s->flags.fast_images && !NO_BLEND_FUNC) { + SrcOverBlendFunc func = qBlendFunctions[d->rasterBuffer->format][img.format()]; + if (func) { + if (!clip) { + d->drawImage(pt, img, func, d->deviceRect, s->intOpacity); + return; + } else if (clip->hasRectClip) { + d->drawImage(pt, img, func, clip->clipRect, s->intOpacity); + return; + } + } + } + + d->image_filler.clip = clip; + d->image_filler.initTexture(&img, s->intOpacity, QTextureData::Plain, img.rect()); + if (!d->image_filler.blend) + return; + d->image_filler.dx = -pt.x(); + d->image_filler.dy = -pt.y(); + QRect rr = img.rect().translated(qRound(pt.x()), qRound(pt.y())); + + fillRect_normalized(rr, &d->image_filler, d); + } + +} + +QRectF qt_mapRect_non_normalizing(const QRectF &r, const QTransform &t) +{ + return QRectF(r.topLeft() * t, r.bottomRight() * t); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawImage(const QRectF &r, const QImage &img, const QRectF &sr, + Qt::ImageConversionFlags) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawImage(), r=" << r << " sr=" << sr << " image=" << img.size() << "depth=" << img.depth(); +#endif + + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + const bool aa = s->flags.antialiased || s->flags.bilinear; + if (!aa && sr.size() == QSize(1, 1)) { + fillRect(r, QColor::fromRgba(img.pixel(sr.x(), sr.y()))); + return; + } + + bool stretch_sr = r.width() != sr.width() || r.height() != sr.height(); + + const QClipData *clip = d->clip(); + + if (s->matrix.type() > QTransform::TxTranslate || stretch_sr) { + + if (s->flags.fast_images) { + SrcOverScaleFunc func = qScaleFunctions[d->rasterBuffer->format][img.format()]; + if (func && (!clip || clip->hasRectClip)) { + func(d->rasterBuffer->buffer(), d->rasterBuffer->bytesPerLine(), + img.bits(), img.bytesPerLine(), + qt_mapRect_non_normalizing(r, s->matrix), sr, + !clip ? d->deviceRect : clip->clipRect, + s->intOpacity); + return; + } + } + + QTransform copy = s->matrix; + copy.translate(r.x(), r.y()); + if (stretch_sr) + copy.scale(r.width() / sr.width(), r.height() / sr.height()); + copy.translate(-sr.x(), -sr.y()); + + d->image_filler_xform.clip = clip; + d->image_filler_xform.initTexture(&img, s->intOpacity, QTextureData::Plain, toAlignedRect_positive(sr)); + if (!d->image_filler_xform.blend) + return; + d->image_filler_xform.setupMatrix(copy, s->flags.bilinear); + +#ifdef QT_FAST_SPANS + ensureState(); + if (s->flags.tx_noshear || s->matrix.type() == QTransform::TxScale) { + d->initializeRasterizer(&d->image_filler_xform); + d->rasterizer->setAntialiased(aa); + + const QPointF offs = aa ? QPointF() : QPointF(aliasedCoordinateDelta, aliasedCoordinateDelta); + + const QRectF &rect = r.normalized(); + const QPointF a = s->matrix.map((rect.topLeft() + rect.bottomLeft()) * 0.5f) - offs; + const QPointF b = s->matrix.map((rect.topRight() + rect.bottomRight()) * 0.5f) - offs; + + if (s->flags.tx_noshear) + d->rasterizer->rasterizeLine(a, b, rect.height() / rect.width()); + else + d->rasterizer->rasterizeLine(a, b, qAbs((s->matrix.m22() * rect.height()) / (s->matrix.m11() * rect.width()))); + return; + } +#endif + bool wasAntialiased = s->flags.antialiased; + if (!s->flags.antialiased) + s->flags.antialiased = s->flags.bilinear; + const qreal offs = s->flags.antialiased ? qreal(0) : aliasedCoordinateDelta; + QPainterPath path; + path.addRect(r); + QTransform m = s->matrix; + s->matrix = QTransform(m.m11(), m.m12(), m.m13(), + m.m21(), m.m22(), m.m23(), + m.m31() - offs, m.m32() - offs, m.m33()); + fillPath(path, &d->image_filler_xform); + s->matrix = m; + s->flags.antialiased = wasAntialiased; + } else { + + if (s->flags.fast_images) { + SrcOverBlendFunc func = qBlendFunctions[d->rasterBuffer->format][img.format()]; + if (func) { + QPointF pt(r.x() + s->matrix.dx(), r.y() + s->matrix.dy()); + if (!clip) { + d->drawImage(pt, img, func, d->deviceRect, s->intOpacity, sr.toRect()); + return; + } else if (clip->hasRectClip) { + d->drawImage(pt, img, func, clip->clipRect, s->intOpacity, sr.toRect()); + return; + } + } + } + + d->image_filler.clip = clip; + d->image_filler.initTexture(&img, s->intOpacity, QTextureData::Plain, toAlignedRect_positive(sr)); + if (!d->image_filler.blend) + return; + d->image_filler.dx = -(r.x() + s->matrix.dx()) + sr.x(); + d->image_filler.dy = -(r.y() + s->matrix.dy()) + sr.y(); + + QRectF rr = r; + rr.translate(s->matrix.dx(), s->matrix.dy()); + fillRect_normalized(toRect_normalized(rr), &d->image_filler, d); + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawTiledPixmap(const QRectF &r, const QPixmap &pixmap, const QPointF &sr) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawTiledPixmap(), r=" << r << "pixmap=" << pixmap.size(); +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + QImage image; + if (pixmap.depth() == 1) + image = d->rasterBuffer->colorizeBitmap(IMAGE_FROM_PIXMAP(pixmap), s->pen.color()); + else + image = IMAGE_FROM_PIXMAP(pixmap); + + if (s->matrix.type() > QTransform::TxTranslate) { + QTransform copy = s->matrix; + copy.translate(r.x(), r.y()); + copy.translate(-sr.x(), -sr.y()); + d->image_filler_xform.clip = d->clip(); + d->image_filler_xform.initTexture(&image, s->intOpacity, QTextureData::Tiled); + if (!d->image_filler_xform.blend) + return; + d->image_filler_xform.setupMatrix(copy, s->flags.bilinear); + +#ifdef QT_FAST_SPANS + ensureState(); + if (s->flags.tx_noshear || s->matrix.type() == QTransform::TxScale) { + d->initializeRasterizer(&d->image_filler_xform); + d->rasterizer->setAntialiased(s->flags.antialiased || s->flags.bilinear); + + const QRectF &rect = r.normalized(); + const QPointF a = s->matrix.map((rect.topLeft() + rect.bottomLeft()) * 0.5f); + const QPointF b = s->matrix.map((rect.topRight() + rect.bottomRight()) * 0.5f); + if (s->flags.tx_noshear) + d->rasterizer->rasterizeLine(a, b, rect.height() / rect.width()); + else + d->rasterizer->rasterizeLine(a, b, qAbs((s->matrix.m22() * rect.height()) / (s->matrix.m11() * rect.width()))); + return; + } +#endif + bool wasAntialiased = s->flags.antialiased; + if (!s->flags.antialiased) + s->flags.antialiased = s->flags.bilinear; + QPainterPath path; + path.addRect(r); + fillPath(path, &d->image_filler_xform); + s->flags.antialiased = wasAntialiased; + } else { + d->image_filler.clip = d->clip(); + + d->image_filler.initTexture(&image, s->intOpacity, QTextureData::Tiled); + if (!d->image_filler.blend) + return; + d->image_filler.dx = -(r.x() + s->matrix.dx()) + sr.x(); + d->image_filler.dy = -(r.y() + s->matrix.dy()) + sr.y(); + + QRectF rr = r; + rr.translate(s->matrix.dx(), s->matrix.dy()); + fillRect_normalized(rr.toRect().normalized(), &d->image_filler, d); + } +} + + +//QWS hack +static inline bool monoVal(const uchar* s, int x) +{ + return (s[x>>3] << (x&7)) & 0x80; +} + +/*! + \internal +*/ +void QRasterPaintEngine::alphaPenBlt(const void* src, int bpl, int depth, int rx,int ry,int w,int h) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + if (!s->penData.blend) + return; + + QRasterBuffer *rb = d->rasterBuffer; + + const QRect rect(rx, ry, w, h); + const QClipData *clip = d->clip(); + bool unclipped = false; + if (clip) { + // inlined QRect::intersects + const bool intersects = qMax(clip->xmin, rect.left()) <= qMin(clip->xmax - 1, rect.right()) + && qMax(clip->ymin, rect.top()) <= qMin(clip->ymax - 1, rect.bottom()); + + if (clip->hasRectClip) { + unclipped = rx > clip->xmin + && rx + w < clip->xmax + && ry > clip->ymin + && ry + h < clip->ymax; + } + + if (!intersects) + return; + } else { + // inlined QRect::intersects + const bool intersects = qMax(0, rect.left()) <= qMin(rb->width() - 1, rect.right()) + && qMax(0, rect.top()) <= qMin(rb->height() - 1, rect.bottom()); + if (!intersects) + return; + + // inlined QRect::contains + const bool contains = rect.left() >= 0 && rect.right() < rb->width() + && rect.top() >= 0 && rect.bottom() < rb->height(); + + unclipped = contains && d->isUnclipped_normalized(rect); + } + + ProcessSpans blend = unclipped ? s->penData.unclipped_blend : s->penData.blend; + const uchar * scanline = static_cast<const uchar *>(src); + + if (s->flags.fast_text) { + if (unclipped) { + if (depth == 1) { + if (s->penData.bitmapBlit) { + s->penData.bitmapBlit(rb, rx, ry, s->penData.solid.color, + scanline, w, h, bpl); + return; + } + } else if (depth == 8) { + if (s->penData.alphamapBlit) { + s->penData.alphamapBlit(rb, rx, ry, s->penData.solid.color, + scanline, w, h, bpl, 0); + return; + } + } else if (depth == 32) { + // (A)RGB Alpha mask where the alpha component is not used. + if (s->penData.alphaRGBBlit) { + s->penData.alphaRGBBlit(rb, rx, ry, s->penData.solid.color, + (const uint *) scanline, w, h, bpl / 4, 0); + return; + } + } + } else if (d->deviceDepth == 32 && (depth == 8 || depth == 32)) { + // (A)RGB Alpha mask where the alpha component is not used. + if (!clip) { + int nx = qMax(0, rx); + int ny = qMax(0, ry); + + // Move scanline pointer to compensate for moved x and y + int xdiff = nx - rx; + int ydiff = ny - ry; + scanline += ydiff * bpl; + scanline += xdiff * (depth == 32 ? 4 : 1); + + w -= xdiff; + h -= ydiff; + + if (nx + w > d->rasterBuffer->width()) + w = d->rasterBuffer->width() - nx; + if (ny + h > d->rasterBuffer->height()) + h = d->rasterBuffer->height() - ny; + + rx = nx; + ry = ny; + } + if (depth == 8 && s->penData.alphamapBlit) { + s->penData.alphamapBlit(rb, rx, ry, s->penData.solid.color, + scanline, w, h, bpl, clip); + } else if (depth == 32 && s->penData.alphaRGBBlit) { + s->penData.alphaRGBBlit(rb, rx, ry, s->penData.solid.color, + (const uint *) scanline, w, h, bpl / 4, clip); + } + return; + } + } + + int x0 = 0; + if (rx < 0) { + x0 = -rx; + w -= x0; + } + + int y0 = 0; + if (ry < 0) { + y0 = -ry; + scanline += bpl * y0; + h -= y0; + } + + w = qMin(w, rb->width() - qMax(0, rx)); + h = qMin(h, rb->height() - qMax(0, ry)); + + if (w <= 0 || h <= 0) + return; + + const int NSPANS = 256; + QSpan spans[NSPANS]; + int current = 0; + + const int x1 = x0 + w; + const int y1 = y0 + h; + + if (depth == 1) { + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ) { + if (!monoVal(scanline, x)) { + ++x; + continue; + } + + if (current == NSPANS) { + blend(current, spans, &s->penData); + current = 0; + } + spans[current].x = x + rx; + spans[current].y = y + ry; + spans[current].coverage = 255; + int len = 1; + ++x; + // extend span until we find a different one. + while (x < x1 && monoVal(scanline, x)) { + ++x; + ++len; + } + spans[current].len = len; + ++current; + } + scanline += bpl; + } + } else if (depth == 8) { + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ) { + // Skip those with 0 coverage + if (scanline[x] == 0) { + ++x; + continue; + } + + if (current == NSPANS) { + blend(current, spans, &s->penData); + current = 0; + } + int coverage = scanline[x]; + spans[current].x = x + rx; + spans[current].y = y + ry; + spans[current].coverage = coverage; + int len = 1; + ++x; + + // extend span until we find a different one. + while (x < x1 && scanline[x] == coverage) { + ++x; + ++len; + } + spans[current].len = len; + ++current; + } + scanline += bpl; + } + } else { // 32-bit alpha... + uint *sl = (uint *) src; + for (int y = y0; y < y1; ++y) { + for (int x = x0; x < x1; ) { + // Skip those with 0 coverage + if ((sl[x] & 0x00ffffff) == 0) { + ++x; + continue; + } + + if (current == NSPANS) { + blend(current, spans, &s->penData); + current = 0; + } + uint rgbCoverage = sl[x]; + int coverage = qGreen(rgbCoverage); + spans[current].x = x + rx; + spans[current].y = y + ry; + spans[current].coverage = coverage; + int len = 1; + ++x; + + // extend span until we find a different one. + while (x < x1 && sl[x] == rgbCoverage) { + ++x; + ++len; + } + spans[current].len = len; + ++current; + } + sl += bpl / sizeof(uint); + } + } +// qDebug() << "alphaPenBlt: num spans=" << current +// << "span:" << spans->x << spans->y << spans->len << spans->coverage; + // Call span func for current set of spans. + if (current != 0) + blend(current, spans, &s->penData); +} + +void QRasterPaintEngine::drawCachedGlyphs(const QPointF &p, const QTextItemInt &ti) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + QTransform matrix = s->matrix; + matrix.translate(p.x(), p.y()); + ti.fontEngine->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + + QFontEngineGlyphCache::Type glyphType = ti.fontEngine->glyphFormat >= 0 ? QFontEngineGlyphCache::Type(ti.fontEngine->glyphFormat) : d->glyphCacheType; + + QImageTextureGlyphCache *cache = + (QImageTextureGlyphCache *) ti.fontEngine->glyphCache(glyphType, s->matrix); + if (!cache) { + cache = new QImageTextureGlyphCache(glyphType, s->matrix); + ti.fontEngine->setGlyphCache(glyphType, cache); + } + + cache->populate(ti, glyphs, positions); + + const QImage &image = cache->image(); + int bpl = image.bytesPerLine(); + + int depth = image.depth(); + int rightShift = 0; + int leftShift = 0; + if (depth == 32) + leftShift = 2; // multiply by 4 + else if (depth == 1) + rightShift = 3; // divide by 8 + + int margin = cache->glyphMargin(); + + const QFixed offs = QFixed::fromReal(aliasedCoordinateDelta); + + const uchar *bits = image.bits(); + for (int i=0; i<glyphs.size(); ++i) { + const QTextureGlyphCache::Coord &c = cache->coords.value(glyphs[i]); + int x = qFloor(positions[i].x + offs) + c.baseLineX - margin; + int y = qFloor(positions[i].y + offs) - c.baseLineY - margin; + +// printf("drawing [%d %d %d %d] baseline [%d %d], glyph: %d, to: %d %d, pos: %d %d\n", +// c.x, c.y, +// c.w, c.h, +// c.baseLineX, c.baseLineY, +// glyphs[i], +// x, y, +// positions[i].x.toInt(), positions[i].y.toInt()); + + alphaPenBlt(bits + ((c.x << leftShift) >> rightShift) + c.y * bpl, bpl, depth, x, y, c.w, c.h); + } + + return; +} + + + +/*! + * Returns true if the rectangle is completly within the current clip + * state of the paint engine. + */ +bool QRasterPaintEnginePrivate::isUnclipped_normalized(const QRect &r) const +{ + const QClipData *cl = clip(); + if (!cl) { + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = deviceRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } + + + // currently all painting functions clips to deviceRect internally + if (cl->clipRect == deviceRect) + return true; + + if (cl->hasRegionClip) { + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = cl->clipRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } else { + return qt_region_strictContains(cl->clipRegion, r); + } +} + +bool QRasterPaintEnginePrivate::isUnclipped(const QRect &rect, + int penWidth) const +{ + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + const QClipData *cl = clip(); + if (!cl) { + QRect r = rect.normalized(); + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = deviceRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } + + + // currently all painting functions that call this function clip to deviceRect internally + if (cl->clipRect == deviceRect) + return true; + + if (s->flags.antialiased) + ++penWidth; + + QRect r = rect.normalized(); + if (penWidth > 0) { + r.setX(r.x() - penWidth); + r.setY(r.y() - penWidth); + r.setWidth(r.width() + 2 * penWidth); + r.setHeight(r.height() + 2 * penWidth); + } + + if (!cl->clipRect.isEmpty()) { + // inline contains() for performance (we know the rects are normalized) + const QRect &r1 = cl->clipRect; + return (r.left() >= r1.left() && r.right() <= r1.right() + && r.top() >= r1.top() && r.bottom() <= r1.bottom()); + } else { + return qt_region_strictContains(cl->clipRegion, r); + } +} + +inline bool QRasterPaintEnginePrivate::isUnclipped(const QRectF &rect, + int penWidth) const +{ + return isUnclipped(rect.normalized().toAlignedRect(), penWidth); +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getBrushFunc(const QRect &rect, + const QSpanData *data) const +{ + return isUnclipped(rect, 0) ? data->unclipped_blend : data->blend; +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getBrushFunc(const QRectF &rect, + const QSpanData *data) const +{ + return isUnclipped(rect, 0) ? data->unclipped_blend : data->blend; +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getPenFunc(const QRect &rect, + const QSpanData *data) const +{ + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + + if (!s->flags.fast_pen && s->matrix.type() > QTransform::TxTranslate) + return data->blend; + const int penWidth = s->flags.fast_pen ? 1 : qCeil(s->pen.widthF()); + return isUnclipped(rect, penWidth) ? data->unclipped_blend : data->blend; +} + +inline ProcessSpans +QRasterPaintEnginePrivate::getPenFunc(const QRectF &rect, + const QSpanData *data) const +{ + Q_Q(const QRasterPaintEngine); + const QRasterPaintEngineState *s = q->state(); + + if (!s->flags.fast_pen && s->matrix.type() > QTransform::TxTranslate) + return data->blend; + const int penWidth = s->flags.fast_pen ? 1 : qCeil(s->lastPen.widthF()); + return isUnclipped(rect, penWidth) ? data->unclipped_blend : data->blend; +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawTextItem(const QPointF &p, const QTextItem &textItem) +{ + const QTextItemInt &ti = static_cast<const QTextItemInt &>(textItem); + QRasterPaintEngineState *s = state(); + +#ifdef QT_DEBUG_DRAW + Q_D(QRasterPaintEngine); + fprintf(stderr," - QRasterPaintEngine::drawTextItem(), (%.2f,%.2f), string=%s ct=%d\n", + p.x(), p.y(), QString::fromRawData(ti.chars, ti.num_chars).toLatin1().data(), + d->glyphCacheType); +#endif + + ensurePen(); + ensureState(); + +#if defined (Q_WS_WIN) || defined(Q_WS_MAC) + + bool drawCached = true; + + if (s->matrix.type() >= QTransform::TxProject) + drawCached = false; + + // don't try to cache huge fonts + if (ti.fontEngine->fontDef.pixelSize * qSqrt(s->matrix.determinant()) >= 64) + drawCached = false; + + // ### Remove the TestFontEngine and Box engine crap, in these + // ### cases we should delegate painting to the font engine + // ### directly... + +#if defined(Q_WS_WIN) && !defined(Q_OS_WINCE) + QFontEngine::Type fontEngineType = ti.fontEngine->type(); + // qDebug() << "type" << fontEngineType << s->matrix.type(); + if ((fontEngineType == QFontEngine::Win && !((QFontEngineWin *) ti.fontEngine)->ttf && s->matrix.type() > QTransform::TxTranslate) + || (s->matrix.type() <= QTransform::TxTranslate + && (fontEngineType == QFontEngine::TestFontEngine + || fontEngineType == QFontEngine::Box))) { + drawCached = false; + } +#else + if (s->matrix.type() > QTransform::TxTranslate) + drawCached = false; +#endif + if (drawCached) { + drawCachedGlyphs(p, ti); + return; + } + +#else // Q_WS_WIN || Q_WS_MAC + + QFontEngine *fontEngine = ti.fontEngine; + +#if defined(Q_WS_QWS) + if (fontEngine->type() == QFontEngine::Box) { + fontEngine->draw(this, qFloor(p.x() + aliasedCoordinateDelta), qFloor(p.y() + aliasedCoordinateDelta), ti); + return; + } + + if (s->matrix.type() < QTransform::TxScale + && (fontEngine->type() == QFontEngine::QPF1 || fontEngine->type() == QFontEngine::QPF2 + || (fontEngine->type() == QFontEngine::Proxy + && !(static_cast<QProxyFontEngine *>(fontEngine)->drawAsOutline())) + )) { + fontEngine->draw(this, qFloor(p.x() + aliasedCoordinateDelta), qFloor(p.y() + aliasedCoordinateDelta), ti); + return; + } +#endif // Q_WS_QWS + +#if (defined(Q_WS_X11) || defined(Q_WS_QWS)) && !defined(QT_NO_FREETYPE) + +#if defined(Q_WS_QWS) && !defined(QT_NO_QWS_QPF2) + if (fontEngine->type() == QFontEngine::QPF2) { + QFontEngine *renderingEngine = static_cast<QFontEngineQPF *>(fontEngine)->renderingEngine(); + if (renderingEngine) + fontEngine = renderingEngine; + } +#endif + + if (fontEngine->type() != QFontEngine::Freetype) { + QPaintEngineEx::drawTextItem(p, ti); + return; + } + + QFontEngineFT *fe = static_cast<QFontEngineFT *>(fontEngine); + + QTransform matrix = s->matrix; + matrix.translate(p.x(), p.y()); + + QVarLengthArray<QFixedPoint> positions; + QVarLengthArray<glyph_t> glyphs; + fe->getGlyphPositions(ti.glyphs, matrix, ti.flags, glyphs, positions); + if (glyphs.size() == 0) + return; + + // only use subpixel antialiasing when drawing to widgets + QFontEngineFT::GlyphFormat neededFormat = + painter()->device()->devType() == QInternal::Widget + ? fe->defaultGlyphFormat() + : QFontEngineFT::Format_A8; + + if (d_func()->mono_surface + || fe->isBitmapFont() // alphaPenBlt can handle mono, too + ) + neededFormat = QFontEngineFT::Format_Mono; + + if (neededFormat == QFontEngineFT::Format_None) + neededFormat = QFontEngineFT::Format_A8; + + QFontEngineFT::QGlyphSet *gset = fe->defaultGlyphs(); + if (s->matrix.type() >= QTransform::TxScale) { + if (s->matrix.isAffine()) + gset = fe->loadTransformedGlyphSet(s->matrix); + else + gset = 0; + + } + + if (!gset || gset->outline_drawing + || !fe->loadGlyphs(gset, glyphs.data(), glyphs.size(), neededFormat)) + { + QPaintEngine::drawTextItem(p, ti); + return; + } + + QFixed offs = QFixed::fromReal(aliasedCoordinateDelta); + FT_Face lockedFace = 0; + + int depth; + switch (neededFormat) { + case QFontEngineFT::Format_Mono: + depth = 1; + break; + case QFontEngineFT::Format_A8: + depth = 8; + break; + case QFontEngineFT::Format_A32: + depth = 32; + break; + default: + Q_ASSERT(false); + }; + + for(int i = 0; i < glyphs.size(); i++) { + QFontEngineFT::Glyph *glyph = gset->glyph_data.value(glyphs[i]); + + if (!glyph || glyph->format != neededFormat) { + if (!lockedFace) + lockedFace = fe->lockFace(); + glyph = fe->loadGlyph(gset, glyphs[i], neededFormat); + } + + if (!glyph || !glyph->data) + continue; + + int pitch; + switch (neededFormat) { + case QFontEngineFT::Format_Mono: + pitch = ((glyph->width + 31) & ~31) >> 3; + break; + case QFontEngineFT::Format_A8: + pitch = (glyph->width + 3) & ~3; + break; + case QFontEngineFT::Format_A32: + pitch = glyph->width * 4; + break; + default: + Q_ASSERT(false); + }; + + alphaPenBlt(glyph->data, pitch, depth, + qFloor(positions[i].x + offs) + glyph->x, + qFloor(positions[i].y + offs) - glyph->y, + glyph->width, glyph->height); + } + if (lockedFace) + fe->unlockFace(); + return; + +#endif +#endif + + QPaintEngineEx::drawTextItem(p, ti); +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawPoints(const QPointF *points, int pointCount) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + qreal pw = s->lastPen.widthF(); + if (!s->flags.fast_pen && (s->matrix.type() > QTransform::TxTranslate || pw > 1)) { + QPaintEngineEx::drawPoints(points, pointCount); + + } else { + if (!s->penData.blend) + return; + + QVarLengthArray<QT_FT_Span, 4096> array(pointCount); + QT_FT_Span span = { 0, 1, 0, 255 }; + const QPointF *end = points + pointCount; + qreal trans_x, trans_y; + int x, y; + int left = d->deviceRect.x(); + int right = left + d->deviceRect.width(); + int top = d->deviceRect.y(); + int bottom = top + d->deviceRect.height(); + int count = 0; + while (points < end) { + s->matrix.map(points->x(), points->y(), &trans_x, &trans_y); + x = qFloor(trans_x); + y = qFloor(trans_y); + if (x >= left && x < right && y >= top && y < bottom) { + if (count > 0) { + const QT_FT_Span &last = array[count - 1]; + // spans must be sorted on y (primary) and x (secondary) + if (y < last.y || (y == last.y && x < last.x)) { + s->penData.blend(count, array.constData(), &s->penData); + count = 0; + } + } + + span.x = x; + span.y = y; + array[count++] = span; + } + ++points; + } + + if (count > 0) + s->penData.blend(count, array.constData(), &s->penData); + } +} + + +void QRasterPaintEngine::drawPoints(const QPoint *points, int pointCount) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + double pw = s->lastPen.widthF(); + if (!s->flags.fast_pen && (s->matrix.type() > QTransform::TxTranslate || pw > 1)) { + QPaintEngineEx::drawPoints(points, pointCount); + + } else { + if (!s->penData.blend) + return; + + QVarLengthArray<QT_FT_Span, 4096> array(pointCount); + QT_FT_Span span = { 0, 1, 0, 255 }; + const QPoint *end = points + pointCount; + qreal trans_x, trans_y; + int x, y; + int left = d->deviceRect.x(); + int right = left + d->deviceRect.width(); + int top = d->deviceRect.y(); + int bottom = top + d->deviceRect.height(); + int count = 0; + while (points < end) { + s->matrix.map(points->x(), points->y(), &trans_x, &trans_y); + x = qFloor(trans_x); + y = qFloor(trans_y); + if (x >= left && x < right && y >= top && y < bottom) { + if (count > 0) { + const QT_FT_Span &last = array[count - 1]; + // spans must be sorted on y (primary) and x (secondary) + if (y < last.y || (y == last.y && x < last.x)) { + s->penData.blend(count, array.constData(), &s->penData); + count = 0; + } + } + + span.x = x; + span.y = y; + array[count++] = span; + } + ++points; + } + + if (count > 0) + s->penData.blend(count, array.constData(), &s->penData); + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawLines(const QLine *lines, int lineCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawLine()"; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + if (s->flags.fast_pen) { + QIntRect bounds; bounds.set(d->deviceRect); + LineDrawMode mode = s->lastPen.capStyle() == Qt::FlatCap + ? LineDrawNormal + : LineDrawIncludeLastPixel; + + int m11 = int(s->matrix.m11()); + int m22 = int(s->matrix.m22()); + int dx = qFloor(s->matrix.dx() + aliasedCoordinateDelta); + int dy = qFloor(s->matrix.dy() + aliasedCoordinateDelta); + int dashOffset = int(s->lastPen.dashOffset()); + for (int i=0; i<lineCount; ++i) { + if (s->flags.int_xform) { + const QLine &l = lines[i]; + int x1 = l.x1() * m11 + dx; + int y1 = l.y1() * m22 + dy; + int x2 = l.x2() * m11 + dx; + int y2 = l.y2() * m22 + dy; + + const QRect brect(QPoint(x1, y1), QPoint(x2, y2)); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(x1, y1, x2, y2, + penBlend, &s->penData, mode, bounds); + else + drawLine_midpoint_dashed_i(x1, y1, x2, y2, + &s->lastPen, penBlend, + &s->penData, mode, bounds, + &dashOffset); + } else { + QLineF line = lines[i] * s->matrix; + const QRectF brect(QPointF(line.x1(), line.y1()), + QPointF(line.x2(), line.y2())); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + penBlend, &s->penData, mode, bounds); + else + drawLine_midpoint_dashed_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + &s->lastPen, penBlend, + &s->penData, mode, bounds, + &dashOffset); + } + } + } else if (s->penData.blend) { + QPaintEngineEx::drawLines(lines, lineCount); + } +} + +void QRasterPaintEnginePrivate::rasterizeLine_dashed(QLineF line, + qreal width, + int *dashIndex, + qreal *dashOffset, + bool *inDash) +{ + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + const QPen &pen = s->lastPen; + const bool squareCap = (pen.capStyle() == Qt::SquareCap); + const QVector<qreal> pattern = pen.dashPattern(); + + qreal length = line.length(); + Q_ASSERT(length > 0); + while (length > 0) { + const bool rasterize = *inDash; + qreal dash = (pattern.at(*dashIndex) - *dashOffset) * width; + QLineF l = line; + + if (dash >= length) { + dash = length; + *dashOffset += dash; + length = 0; + } else { + *dashOffset = 0; + *inDash = !(*inDash); + *dashIndex = (*dashIndex + 1) % pattern.size(); + length -= dash; + l.setLength(dash); + line.setP1(l.p2()); + } + + if (rasterize && dash != 0) + rasterizer->rasterizeLine(l.p1(), l.p2(), width / dash, squareCap); + } +} + +/*! + \reimp +*/ +void QRasterPaintEngine::drawLines(const QLineF *lines, int lineCount) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - QRasterPaintEngine::drawLine()"; +#endif + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + if (!s->penData.blend) + return; + if (s->flags.fast_pen) { + QIntRect bounds; + bounds.set(d->deviceRect); + LineDrawMode mode = s->lastPen.capStyle() == Qt::FlatCap + ? LineDrawNormal + : LineDrawIncludeLastPixel; + + int dashOffset = int(s->lastPen.dashOffset()); + for (int i=0; i<lineCount; ++i) { + QLineF line = (lines[i] * s->matrix).translated(aliasedCoordinateDelta, aliasedCoordinateDelta); + const QRectF brect(QPointF(line.x1(), line.y1()), + QPointF(line.x2(), line.y2())); + ProcessSpans penBlend = d->getPenFunc(brect, &s->penData); + if (qpen_style(s->lastPen) == Qt::SolidLine) + drawLine_midpoint_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + penBlend, &s->penData, mode, bounds); + else + drawLine_midpoint_dashed_i(int(line.x1()), int(line.y1()), + int(line.x2()), int(line.y2()), + &s->lastPen, + penBlend, &s->penData, mode, + bounds, &dashOffset); + } + } else { + QPaintEngineEx::drawLines(lines, lineCount); + } +} + + +/*! + \reimp +*/ +void QRasterPaintEngine::drawEllipse(const QRectF &rect) +{ + Q_D(QRasterPaintEngine); + QRasterPaintEngineState *s = state(); + + ensurePen(); + if (((qpen_style(s->lastPen) == Qt::SolidLine && s->flags.fast_pen) + || (qpen_style(s->lastPen) == Qt::NoPen && !s->flags.antialiased)) +#ifdef FLOATING_POINT_BUGGY_OR_NO_FPU + && qMax(rect.width(), rect.height()) < 128 // integer math breakdown +#endif + && s->matrix.type() <= QTransform::TxScale) // no shear + { + ensureBrush(); + const QRectF r = s->matrix.mapRect(rect); + ProcessSpans penBlend = d->getPenFunc(r, &s->penData); + ProcessSpans brushBlend = d->getBrushFunc(r, &s->brushData); + const QRect brect = QRect(int(r.x()), int(r.y()), + int_dim(r.x(), r.width()), + int_dim(r.y(), r.height())); + if (brect == r) { + drawEllipse_midpoint_i(brect, d->deviceRect, penBlend, brushBlend, + &s->penData, &s->brushData); + return; + } + } + QPaintEngineEx::drawEllipse(rect); +} + +/*! + \internal +*/ +#ifdef Q_WS_MAC +void QRasterPaintEngine::setCGContext(CGContextRef ctx) +{ + Q_D(QRasterPaintEngine); + d->cgContext = ctx; +} + +/*! + \internal +*/ +CGContextRef QRasterPaintEngine::getCGContext() const +{ + Q_D(const QRasterPaintEngine); + return d->cgContext; +} +#endif + +#ifdef Q_WS_WIN +/*! + \internal +*/ +void QRasterPaintEngine::setDC(HDC hdc) { + Q_D(QRasterPaintEngine); + d->hdc = hdc; +} + +/*! + \internal +*/ +HDC QRasterPaintEngine::getDC() const +{ + Q_D(const QRasterPaintEngine); + return d->hdc; +} + +/*! + \internal +*/ +void QRasterPaintEngine::releaseDC(HDC) const +{ +} + +#endif + +/*! + \internal +*/ +QPoint QRasterPaintEngine::coordinateOffset() const +{ + return QPoint(0, 0); +} + +/*! + Draws the given color \a spans with the specified \a color. The \a + count parameter specifies the number of spans. + + The default implementation does nothing; reimplement this function + to draw the given color \a spans with the specified \a color. Note + that this function \e must be reimplemented if the framebuffer is + not memory-mapped. + + \sa drawBufferSpan() +*/ +#if defined(Q_WS_QWS) && !defined(QT_NO_RASTERCALLBACKS) +void QRasterPaintEngine::drawColorSpans(const QSpan *spans, int count, uint color) +{ + Q_UNUSED(spans); + Q_UNUSED(count); + Q_UNUSED(color); + qFatal("QRasterPaintEngine::drawColorSpans must be reimplemented on " + "a non memory-mapped device"); +} + +/*! + \fn void QRasterPaintEngine::drawBufferSpan(const uint *buffer, int size, int x, int y, int length, uint alpha) + + Draws the given \a buffer. + + The default implementation does nothing; reimplement this function + to draw a buffer that contains more than one color. Note that this + function \e must be reimplemented if the framebuffer is not + memory-mapped. + + The \a size parameter specifies the total size of the given \a + buffer, while the \a length parameter specifies the number of + pixels to draw. The buffer's position is given by (\a x, \a + y). The provided \a alpha value is added to each pixel in the + buffer when drawing. + + \sa drawColorSpans() +*/ +void QRasterPaintEngine::drawBufferSpan(const uint *buffer, int bufsize, + int x, int y, int length, uint const_alpha) +{ + Q_UNUSED(buffer); + Q_UNUSED(bufsize); + Q_UNUSED(x); + Q_UNUSED(y); + Q_UNUSED(length); + Q_UNUSED(const_alpha); + qFatal("QRasterPaintEngine::drawBufferSpan must be reimplemented on " + "a non memory-mapped device"); +} +#endif // Q_WS_QWS + +void QRasterPaintEngine::drawBitmap(const QPointF &pos, const QPixmap &pm, QSpanData *fg) +{ + Q_ASSERT(fg); + if (!fg->blend) + return; + Q_D(QRasterPaintEngine); + + const QImage image = IMAGE_FROM_PIXMAP(pm); + Q_ASSERT(image.depth() == 1); + + const int spanCount = 256; + QT_FT_Span spans[spanCount]; + int n = 0; + + // Boundaries + int w = pm.width(); + int h = pm.height(); + int ymax = qMin(qRound(pos.y() + h), d->rasterBuffer->height()); + int ymin = qMax(qRound(pos.y()), 0); + int xmax = qMin(qRound(pos.x() + w), d->rasterBuffer->width()); + int xmin = qMax(qRound(pos.x()), 0); + + int x_offset = xmin - qRound(pos.x()); + + QImage::Format format = image.format(); + for (int y = ymin; y < ymax; ++y) { + const uchar *src = image.scanLine(y - qRound(pos.y())); + if (format == QImage::Format_MonoLSB) { + for (int x = 0; x < xmax - xmin; ++x) { + int src_x = x + x_offset; + uchar pixel = src[src_x >> 3]; + if (!pixel) { + x += 7 - (src_x%8); + continue; + } + if (pixel & (0x1 << (src_x & 7))) { + spans[n].x = xmin + x; + spans[n].y = y; + spans[n].coverage = 255; + int len = 1; + while (src_x < w-1 && src[(src_x+1) >> 3] & (0x1 << ((src_x+1) & 7))) { + ++src_x; + ++len; + } + spans[n].len = ((len + spans[n].x) > xmax) ? (xmax - spans[n].x) : len; + x += len; + ++n; + if (n == spanCount) { + fg->blend(n, spans, fg); + n = 0; + } + } + } + } else { + for (int x = 0; x < xmax - xmin; ++x) { + int src_x = x + x_offset; + uchar pixel = src[src_x >> 3]; + if (!pixel) { + x += 7 - (src_x%8); + continue; + } + if (pixel & (0x80 >> (x & 7))) { + spans[n].x = xmin + x; + spans[n].y = y; + spans[n].coverage = 255; + int len = 1; + while (src_x < w-1 && src[(src_x+1) >> 3] & (0x80 >> ((src_x+1) & 7))) { + ++src_x; + ++len; + } + spans[n].len = ((len + spans[n].x) > xmax) ? (xmax - spans[n].x) : len; + x += len; + ++n; + if (n == spanCount) { + fg->blend(n, spans, fg); + n = 0; + } + } + } + } + } + if (n) { + fg->blend(n, spans, fg); + n = 0; + } +} + +/*! + \enum QRasterPaintEngine::ClipType + \internal + + \value RectClip Indicates that the currently set clip is a single rectangle. + \value ComplexClip Indicates that the currently set clip is a combination of several shapes. +*/ + +/*! + \internal + Returns the type of the clip currently set. +*/ +QRasterPaintEngine::ClipType QRasterPaintEngine::clipType() const +{ + Q_D(const QRasterPaintEngine); + + const QClipData *clip = d->clip(); + if (!clip || clip->hasRectClip) + return RectClip; + else + return ComplexClip; +} + +/*! + \internal + Returns the bounding rect of the currently set clip. +*/ +QRect QRasterPaintEngine::clipBoundingRect() const +{ + Q_D(const QRasterPaintEngine); + + const QClipData *clip = d->clip(); + + if (!clip) + return d->deviceRect; + + if (clip->hasRectClip) + return clip->clipRect; + + return QRect(clip->xmin, clip->ymin, clip->xmax - clip->xmin, clip->ymax - clip->ymin); +} + +static void qt_merge_clip(const QClipData *c1, const QClipData *c2, QClipData *result) +{ + Q_ASSERT(c1->clipSpanHeight == c2->clipSpanHeight && c1->clipSpanHeight == result->clipSpanHeight); + + // ### buffer overflow possible + const int BUFFER_SIZE = 4096; + int buffer[BUFFER_SIZE]; + int *b = buffer; + int bsize = BUFFER_SIZE; + + QClipData::ClipLine *c1ClipLines = const_cast<QClipData *>(c1)->clipLines(); + QClipData::ClipLine *c2ClipLines = const_cast<QClipData *>(c2)->clipLines(); + result->initialize(); + + for (int y = 0; y < c1->clipSpanHeight; ++y) { + const QSpan *c1_spans = c1ClipLines[y].spans; + int c1_count = c1ClipLines[y].count; + const QSpan *c2_spans = c2ClipLines[y].spans; + int c2_count = c2ClipLines[y].count; + + if (c1_count == 0 && c2_count == 0) + continue; + if (c1_count == 0) { + result->appendSpans(c2_spans, c2_count); + continue; + } else if (c2_count == 0) { + result->appendSpans(c1_spans, c1_count); + continue; + } + + // we need to merge the two + + // find required length + int max = qMax(c1_spans[c1_count - 1].x + c1_spans[c1_count - 1].len, + c2_spans[c2_count - 1].x + c2_spans[c2_count - 1].len); + if (max > bsize) { + b = (int *)realloc(bsize == BUFFER_SIZE ? 0 : b, max*sizeof(int)); + bsize = max; + } + memset(buffer, 0, BUFFER_SIZE * sizeof(int)); + + // Fill with old spans. + for (int i = 0; i < c1_count; ++i) { + const QSpan *cs = c1_spans + i; + for (int j=cs->x; j<cs->x + cs->len; ++j) + buffer[j] = cs->coverage; + } + + // Fill with new spans + for (int i = 0; i < c2_count; ++i) { + const QSpan *cs = c2_spans + i; + for (int j = cs->x; j < cs->x + cs->len; ++j) { + buffer[j] += cs->coverage; + if (buffer[j] > 255) + buffer[j] = 255; + } + } + + int x = 0; + while (x<max) { + + // Skip to next span + while (x < max && buffer[x] == 0) ++x; + if (x >= max) break; + + int sx = x; + int coverage = buffer[x]; + + // Find length of span + while (x < max && buffer[x] == coverage) + ++x; + + result->appendSpan(sx, x - sx, y, coverage); + } + } + if (b != buffer) + free(b); +} + +void QRasterPaintEnginePrivate::initializeRasterizer(QSpanData *data) +{ + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + rasterizer->setAntialiased(s->flags.antialiased); + + QRect clipRect(deviceRect); + ProcessSpans blend; + // ### get from optimized rectbased QClipData + + const QClipData *c = clip(); + if (c) { + const QRect r(QPoint(c->xmin, c->ymin), + QPoint(c->xmax, c->ymax)); + clipRect = clipRect.intersected(r); + blend = data->blend; + } else { + blend = data->unclipped_blend; + } + + rasterizer->setClipRect(clipRect); + rasterizer->initialize(blend, data); +} + +void QRasterPaintEnginePrivate::rasterize(QT_FT_Outline *outline, + ProcessSpans callback, + QSpanData *spanData, QRasterBuffer *rasterBuffer) +{ + if (!callback || !outline) + return; + + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + if (!s->flags.antialiased) { + initializeRasterizer(spanData); + + const Qt::FillRule fillRule = outline->flags == QT_FT_OUTLINE_NONE + ? Qt::WindingFill + : Qt::OddEvenFill; + + rasterizer->rasterize(outline, fillRule); + return; + } + + rasterize(outline, callback, (void *)spanData, rasterBuffer); +} + +void QRasterPaintEnginePrivate::rasterize(QT_FT_Outline *outline, + ProcessSpans callback, + void *userData, QRasterBuffer *) +{ + if (!callback || !outline) + return; + + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + if (!s->flags.antialiased) { + rasterizer->setAntialiased(s->flags.antialiased); + rasterizer->setClipRect(deviceRect); + rasterizer->initialize(callback, userData); + + const Qt::FillRule fillRule = outline->flags == QT_FT_OUTLINE_NONE + ? Qt::WindingFill + : Qt::OddEvenFill; + + rasterizer->rasterize(outline, fillRule); + return; + } + + void *data = userData; + + QT_FT_BBox clip_box = { deviceRect.x(), + deviceRect.y(), + deviceRect.x() + deviceRect.width(), + deviceRect.y() + deviceRect.height() }; + + QT_FT_Raster_Params rasterParams; + rasterParams.target = 0; + rasterParams.source = outline; + rasterParams.flags = QT_FT_RASTER_FLAG_CLIP; + rasterParams.gray_spans = 0; + rasterParams.black_spans = 0; + rasterParams.bit_test = 0; + rasterParams.bit_set = 0; + rasterParams.user = data; + rasterParams.clip_box = clip_box; + + bool done = false; + int error; + + while (!done) { + + rasterParams.flags |= (QT_FT_RASTER_FLAG_AA | QT_FT_RASTER_FLAG_DIRECT); + rasterParams.gray_spans = callback; + error = qt_ft_grays_raster.raster_render(*grayRaster, &rasterParams); + + // Out of memory, reallocate some more and try again... + if (error == -6) { // -6 is Result_err_OutOfMemory + int new_size = rasterPoolSize * 2; + if (new_size > 1024 * 1024) { + qWarning("QPainter: Rasterization of primitive failed"); + return; + } + +#if defined(Q_WS_WIN64) + _aligned_free(rasterPoolBase); +#else + free(rasterPoolBase); +#endif + + rasterPoolSize = new_size; + rasterPoolBase = +#if defined(Q_WS_WIN64) + // We make use of setjmp and longjmp in qgrayraster.c which requires + // 16-byte alignment, hence we hardcode this requirement here.. + (unsigned char *) _aligned_malloc(rasterPoolSize, sizeof(void*) * 2); +#else + (unsigned char *) malloc(rasterPoolSize); +#endif + + qt_ft_grays_raster.raster_done(*grayRaster); + qt_ft_grays_raster.raster_new(0, grayRaster); + qt_ft_grays_raster.raster_reset(*grayRaster, rasterPoolBase, rasterPoolSize); + } else { + done = true; + } + } +} + +void QRasterPaintEnginePrivate::recalculateFastImages() +{ + Q_Q(QRasterPaintEngine); + QRasterPaintEngineState *s = q->state(); + + s->flags.fast_images = !(s->renderHints & QPainter::SmoothPixmapTransform) + && rasterBuffer->compositionMode == QPainter::CompositionMode_SourceOver + && s->matrix.type() <= QTransform::TxScale; +} + + + +QImage QRasterBuffer::colorizeBitmap(const QImage &image, const QColor &color) +{ + Q_ASSERT(image.depth() == 1); + + QImage sourceImage = image.convertToFormat(QImage::Format_MonoLSB); + QImage dest = QImage(sourceImage.size(), QImage::Format_ARGB32_Premultiplied); + + QRgb fg = PREMUL(color.rgba()); + QRgb bg = 0; + + int height = sourceImage.height(); + int width = sourceImage.width(); + for (int y=0; y<height; ++y) { + uchar *source = sourceImage.scanLine(y); + QRgb *target = reinterpret_cast<QRgb *>(dest.scanLine(y)); + for (int x=0; x < width; ++x) + target[x] = (source[x>>3] >> (x&7)) & 1 ? fg : bg; + } + return dest; +} + +QRasterBuffer::~QRasterBuffer() +{ +} + +void QRasterBuffer::init() +{ + compositionMode = QPainter::CompositionMode_SourceOver; + monoDestinationWithClut = false; + destColor0 = 0; + destColor1 = 0; +} + +QImage::Format QRasterBuffer::prepare(QImage *image) +{ + m_buffer = (uchar *)image->bits(); + m_width = qMin(QT_RASTER_COORD_LIMIT, image->width()); + m_height = qMin(QT_RASTER_COORD_LIMIT, image->height()); + bytes_per_pixel = image->depth()/8; + bytes_per_line = image->bytesPerLine(); + + format = image->format(); + drawHelper = qDrawHelper + format; + if (image->depth() == 1 && image->colorTable().size() == 2) { + monoDestinationWithClut = true; + destColor0 = PREMUL(image->colorTable()[0]); + destColor1 = PREMUL(image->colorTable()[1]); + } + + return format; +} + +void QRasterBuffer::resetBuffer(int val) +{ + memset(m_buffer, val, m_height*bytes_per_line); +} + + +#if defined(Q_WS_QWS) +void QRasterBuffer::prepare(QCustomRasterPaintDevice *device) +{ + m_buffer = reinterpret_cast<uchar*>(device->memory()); + m_width = qMin(QT_RASTER_COORD_LIMIT, device->width()); + m_height = qMin(QT_RASTER_COORD_LIMIT, device->height()); + bytes_per_pixel = device->depth() / 8; + bytes_per_line = device->bytesPerLine(); + format = device->format(); +#ifndef QT_NO_RASTERCALLBACKS + if (!m_buffer) + drawHelper = qDrawHelperCallback + format; + else +#endif + drawHelper = qDrawHelper + format; +} + +class MetricAccessor : public QWidget { +public: + int metric(PaintDeviceMetric m) { + return QWidget::metric(m); + } +}; + +int QCustomRasterPaintDevice::metric(PaintDeviceMetric m) const +{ + switch (m) { + case PdmWidth: + return widget->frameGeometry().width(); + case PdmHeight: + return widget->frameGeometry().height(); + default: + break; + } + + return (static_cast<MetricAccessor*>(widget)->metric(m)); +} + +int QCustomRasterPaintDevice::bytesPerLine() const +{ + return (width() * depth() + 7) / 8; +} +#endif // Q_WS_QWS + + +/*! + \class QCustomRasterPaintDevice + \preliminary + \ingroup qws + \since 4.2 + + \brief The QCustomRasterPaintDevice class is provided to activate + hardware accelerated paint engines in Qt for Embedded Linux. + + Note that this class is only available in \l{Qt for Embedded Linux}. + + In \l{Qt for Embedded Linux}, painting is a pure software + implementation. But starting with Qt 4.2, it is + possible to add an accelerated graphics driver to take advantage + of available hardware resources. + + Hardware acceleration is accomplished by creating a custom screen + driver, accelerating the copying from memory to the screen, and + implementing a custom paint engine accelerating the various + painting operations. Then a custom paint device (derived from the + QCustomRasterPaintDevice class) and a custom window surface + (derived from QWSWindowSurface) must be implemented to make + \l{Qt for Embedded Linux} aware of the accelerated driver. + + See the \l {Adding an Accelerated Graphics Driver to Qt for Embedded Linux} + documentation for details. + + \sa QRasterPaintEngine, QPaintDevice +*/ + +/*! + \fn QCustomRasterPaintDevice::QCustomRasterPaintDevice(QWidget *widget) + + Constructs a custom raster based paint device for the given + top-level \a widget. +*/ + +/*! + \fn int QCustomRasterPaintDevice::bytesPerLine() const + + Returns the number of bytes per line in the framebuffer. Note that + this number might be larger than the framebuffer width. +*/ + +/*! + \fn int QCustomRasterPaintDevice::devType() const + \internal +*/ + +/*! + \fn QImage::Format QCustomRasterPaintDevice::format() const + + Returns the format of the device's memory buffet. + + The default format is QImage::Format_ARGB32_Premultiplied. The + only other valid format is QImage::Format_RGB16. +*/ + +/*! + \fn void * QCustomRasterPaintDevice::memory () const + + Returns a pointer to the paint device's memory buffer, or 0 if no + such buffer exists. +*/ + +/*! + \fn int QCustomRasterPaintDevice::metric ( PaintDeviceMetric m ) const + \reimp +*/ + +/*! + \fn QSize QCustomRasterPaintDevice::size () const + \internal +*/ + + +QClipData::QClipData(int height) +{ + clipSpanHeight = height; + m_clipLines = 0; + + allocated = height; + m_spans = 0; + xmin = xmax = ymin = ymax = 0; + count = 0; + + enabled = true; + hasRectClip = hasRegionClip = false; +} + +QClipData::~QClipData() +{ + if (m_clipLines) + free(m_clipLines); + if (m_spans) + free(m_spans); +} + +void QClipData::initialize() +{ + if (m_spans) + return; + + m_clipLines = (ClipLine *)calloc(sizeof(ClipLine), clipSpanHeight); + m_spans = (QSpan *)malloc(clipSpanHeight*sizeof(QSpan)); + + if (hasRectClip) { + int y = 0; + while (y < ymin) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + + const int len = clipRect.width(); + count = 0; + while (y < ymax) { + QSpan *span = m_spans + count; + span->x = xmin; + span->len = len; + span->y = y; + span->coverage = 255; + ++count; + + m_clipLines[y].spans = span; + m_clipLines[y].count = 1; + ++y; + } + + while (y < clipSpanHeight) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + } else if (hasRegionClip) { + + const QVector<QRect> rects = clipRegion.rects(); + const int numRects = rects.size(); + + { // resize + const int maxSpans = (ymax - ymin) * numRects; + if (maxSpans > allocated) { + m_spans = (QSpan *)realloc(m_spans, maxSpans * sizeof(QSpan)); + allocated = maxSpans; + } + } + + int y = 0; + int firstInBand = 0; + while (firstInBand < numRects) { + const int currMinY = rects.at(firstInBand).y(); + const int currMaxY = currMinY + rects.at(firstInBand).height(); + + while (y < currMinY) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + + int lastInBand = firstInBand; + while (lastInBand + 1 < numRects && rects.at(lastInBand+1).top() == y) + ++lastInBand; + + while (y < currMaxY) { + + m_clipLines[y].spans = m_spans + count; + m_clipLines[y].count = lastInBand - firstInBand + 1; + + for (int r = firstInBand; r <= lastInBand; ++r) { + const QRect &currRect = rects.at(r); + QSpan *span = m_spans + count; + span->x = currRect.x(); + span->len = currRect.width(); + span->y = y; + span->coverage = 255; + ++count; + } + ++y; + } + + firstInBand = lastInBand + 1; + } + + Q_ASSERT(count <= allocated); + + while (y < clipSpanHeight) { + m_clipLines[y].spans = 0; + m_clipLines[y].count = 0; + ++y; + } + + } +} + +void QClipData::fixup() +{ + Q_ASSERT(m_spans); + + if (count == 0) { + ymin = ymax = xmin = xmax = 0; + return; + } + +// qDebug("QClipData::fixup: count=%d",count); + int y = -1; + ymin = m_spans[0].y; + ymax = m_spans[count-1].y + 1; + xmin = INT_MAX; + xmax = 0; + for (int i = 0; i < count; ++i) { +// qDebug() << " " << spans[i].x << spans[i].y << spans[i].len << spans[i].coverage; + if (m_spans[i].y != y) { + y = m_spans[i].y; + m_clipLines[y].spans = m_spans+i; + m_clipLines[y].count = 0; +// qDebug() << " new line: y=" << y; + } + ++m_clipLines[y].count; + xmin = qMin(xmin, (int)m_spans[i].x); + xmax = qMax(xmax, (int)m_spans[i].x + m_spans[i].len); + } + ++xmax; +// qDebug("xmin=%d,xmax=%d,ymin=%d,ymax=%d", xmin, xmax, ymin, ymax); +} + +/* + Convert \a rect to clip spans. + */ +void QClipData::setClipRect(const QRect &rect) +{ +// qDebug() << "setClipRect" << clipSpanHeight << count << allocated << rect; + hasRectClip = true; + clipRect = rect; + + xmin = rect.x(); + xmax = rect.x() + rect.width(); + ymin = qMin(rect.y(), clipSpanHeight); + ymax = qMin(rect.y() + rect.height(), clipSpanHeight); + +// qDebug() << xmin << xmax << ymin << ymax; +} + +/* + Convert \a region to clip spans. + */ +void QClipData::setClipRegion(const QRegion ®ion) +{ + if (region.numRects() == 1) { + setClipRect(region.rects().at(0)); + return; + } + + hasRegionClip = true; + clipRegion = region; + + { // set bounding rect + const QRect rect = region.boundingRect(); + xmin = rect.x(); + xmax = rect.x() + rect.width(); + ymin = rect.y(); + ymax = rect.y() + rect.height(); + } +} + +/*! + \internal + spans must be sorted on y +*/ +static const QSpan *qt_intersect_spans(const QClipData *clip, int *currentClip, + const QSpan *spans, const QSpan *end, + QSpan **outSpans, int available) +{ + const_cast<QClipData *>(clip)->initialize(); + + QSpan *out = *outSpans; + + const QSpan *clipSpans = clip->m_spans + *currentClip; + const QSpan *clipEnd = clip->m_spans + clip->count; + + while (available && spans < end ) { + if (clipSpans >= clipEnd) { + spans = end; + break; + } + if (clipSpans->y > spans->y) { + ++spans; + continue; + } + if (spans->y != clipSpans->y) { + if (spans->y < clip->count && clip->m_clipLines[spans->y].spans) + clipSpans = clip->m_clipLines[spans->y].spans; + else + ++clipSpans; + continue; + } + Q_ASSERT(spans->y == clipSpans->y); + + int sx1 = spans->x; + int sx2 = sx1 + spans->len; + int cx1 = clipSpans->x; + int cx2 = cx1 + clipSpans->len; + + if (cx1 < sx1 && cx2 < sx1) { + ++clipSpans; + continue; + } else if (sx1 < cx1 && sx2 < cx1) { + ++spans; + continue; + } + int x = qMax(sx1, cx1); + int len = qMin(sx2, cx2) - x; + if (len) { + out->x = qMax(sx1, cx1); + out->len = qMin(sx2, cx2) - out->x; + out->y = spans->y; + out->coverage = qt_div_255(spans->coverage * clipSpans->coverage); + ++out; + --available; + } + if (sx2 < cx2) { + ++spans; + } else { + ++clipSpans; + } + } + + *outSpans = out; + *currentClip = clipSpans - clip->m_spans; + return spans; +} + +static void qt_span_fill_clipped(int spanCount, const QSpan *spans, void *userData) +{ +// qDebug() << "qt_span_fill_clipped" << spanCount; + QSpanData *fillData = reinterpret_cast<QSpanData *>(userData); + + Q_ASSERT(fillData->blend && fillData->unclipped_blend); + + const int NSPANS = 256; + QSpan cspans[NSPANS]; + int currentClip = 0; + const QSpan *end = spans + spanCount; + while (spans < end) { + QSpan *clipped = cspans; + spans = qt_intersect_spans(fillData->clip, ¤tClip, spans, end, &clipped, NSPANS); +// qDebug() << "processed " << processed << "clipped" << clipped-cspans +// << "span:" << cspans->x << cspans->y << cspans->len << spans->coverage; + + if (clipped - cspans) + fillData->unclipped_blend(clipped - cspans, cspans, fillData); + } +} + +/* + \internal + Clip spans to \a{clip}-rectangle. + Returns number of unclipped spans +*/ +static int qt_intersect_spans(QT_FT_Span *spans, int numSpans, + const QRect &clip) +{ + const short minx = clip.left(); + const short miny = clip.top(); + const short maxx = clip.right(); + const short maxy = clip.bottom(); + + int n = 0; + for (int i = 0; i < numSpans; ++i) { + if (spans[i].y > maxy) + break; + if (spans[i].y < miny + || spans[i].x > maxx + || spans[i].x + spans[i].len <= minx) { + continue; + } + if (spans[i].x < minx) { + spans[n].len = qMin(spans[i].len - (minx - spans[i].x), maxx - minx + 1); + spans[n].x = minx; + } else { + spans[n].x = spans[i].x; + spans[n].len = qMin(spans[i].len, ushort(maxx - spans[n].x + 1)); + } + if (spans[n].len == 0) + continue; + spans[n].y = spans[i].y; + spans[n].coverage = spans[i].coverage; + ++n; + } + return n; +} + +/* + \internal + Clip spans to \a{clip}-region. + Returns number of unclipped spans +*/ +static int qt_intersect_spans(QT_FT_Span *spans, int numSpans, + int *currSpan, + QT_FT_Span *outSpans, int maxOut, + const QRegion &clip) +{ + const QVector<QRect> rects = clip.rects(); + const int numRects = rects.size(); + + int r = 0; + short miny, minx, maxx, maxy; + { + const QRect &rect = rects[0]; + miny = rect.top(); + minx = rect.left(); + maxx = rect.right(); + maxy = rect.bottom(); + } + + // TODO: better mapping of currY and startRect + + int n = 0; + int i = *currSpan; + int currY = spans[i].y; + while (i < numSpans) { + + if (spans[i].y != currY && r != 0) { + currY = spans[i].y; + r = 0; + const QRect &rect = rects[r]; + miny = rect.top(); + minx = rect.left(); + maxx = rect.right(); + maxy = rect.bottom(); + } + + if (spans[i].y < miny) { + ++i; + continue; + } + + if (spans[i].y > maxy || spans[i].x > maxx) { + if (++r >= numRects) { + ++i; + continue; + } + + const QRect &rect = rects[r]; + miny = rect.top(); + minx = rect.left(); + maxx = rect.right(); + maxy = rect.bottom(); + continue; + } + + if (spans[i].x + spans[i].len <= minx) { + ++i; + continue; + } + + outSpans[n].y = spans[i].y; + outSpans[n].coverage = spans[i].coverage; + + if (spans[i].x < minx) { + const ushort cutaway = minx - spans[i].x; + outSpans[n].len = qMin(spans[i].len - cutaway, maxx - minx + 1); + outSpans[n].x = minx; + if (outSpans[n].len == spans[i].len - cutaway) { + ++i; + } else { + // span wider than current rect + spans[i].len -= outSpans[n].len + cutaway; + spans[i].x = maxx + 1; + } + } else { // span starts inside current rect + outSpans[n].x = spans[i].x; + outSpans[n].len = qMin(spans[i].len, + ushort(maxx - spans[i].x + 1)); + if (outSpans[n].len == spans[i].len) { + ++i; + } else { + // span wider than current rect + spans[i].len -= outSpans[n].len; + spans[i].x = maxx + 1; + } + } + + if (++n >= maxOut) + break; + } + + *currSpan = i; + return n; +} + +static void qt_span_fill_clipRect(int count, const QSpan *spans, + void *userData) +{ + QSpanData *fillData = reinterpret_cast<QSpanData *>(userData); + Q_ASSERT(fillData->blend && fillData->unclipped_blend); + + Q_ASSERT(fillData->clip); + Q_ASSERT(!fillData->clip->clipRect.isEmpty()); + + // hw: check if this const_cast<> is safe!!! + count = qt_intersect_spans(const_cast<QSpan*>(spans), count, + fillData->clip->clipRect); + if (count > 0) + fillData->unclipped_blend(count, spans, fillData); +} + +static void qt_span_fill_clipRegion(int count, const QSpan *spans, + void *userData) +{ + QSpanData *fillData = reinterpret_cast<QSpanData *>(userData); + Q_ASSERT(fillData->blend && fillData->unclipped_blend); + + Q_ASSERT(fillData->clip); + Q_ASSERT(!fillData->clip->clipRegion.isEmpty()); + + const int NSPANS = 256; + QSpan cspans[NSPANS]; + int currentClip = 0; + while (currentClip < count) { + const int unclipped = qt_intersect_spans(const_cast<QSpan*>(spans), + count, ¤tClip, + &cspans[0], NSPANS, + fillData->clip->clipRegion); + if (unclipped > 0) + fillData->unclipped_blend(unclipped, cspans, fillData); + } + +} + +static void qt_span_clip(int count, const QSpan *spans, void *userData) +{ + ClipData *clipData = reinterpret_cast<ClipData *>(userData); + +// qDebug() << " qt_span_clip: " << count << clipData->operation; +// for (int i = 0; i < qMin(count, 10); ++i) { +// qDebug() << " " << spans[i].x << spans[i].y << spans[i].len << spans[i].coverage; +// } + + switch (clipData->operation) { + + case Qt::IntersectClip: + { + QClipData *newClip = clipData->newClip; + newClip->initialize(); + + int currentClip = 0; + const QSpan *end = spans + count; + while (spans < end) { + QSpan *newspans = newClip->m_spans + newClip->count; + spans = qt_intersect_spans(clipData->oldClip, ¤tClip, spans, end, + &newspans, newClip->allocated - newClip->count); + newClip->count = newspans - newClip->m_spans; + if (spans < end) { + newClip->allocated *= 2; + newClip->m_spans = (QSpan *)realloc(newClip->m_spans, newClip->allocated*sizeof(QSpan)); + } + } + } + break; + + case Qt::UniteClip: + case Qt::ReplaceClip: + clipData->newClip->appendSpans(spans, count); + break; + case Qt::NoClip: + break; + } +} + +#ifndef QT_NO_DEBUG +QImage QRasterBuffer::bufferImage() const +{ + QImage image(m_width, m_height, QImage::Format_ARGB32_Premultiplied); + + for (int y = 0; y < m_height; ++y) { + uint *span = (uint *)const_cast<QRasterBuffer *>(this)->scanLine(y); + + for (int x=0; x<m_width; ++x) { + uint argb = span[x]; + image.setPixel(x, y, argb); + } + } + return image; +} +#endif + + +void QRasterBuffer::flushToARGBImage(QImage *target) const +{ + int w = qMin(m_width, target->width()); + int h = qMin(m_height, target->height()); + + for (int y=0; y<h; ++y) { + uint *sourceLine = (uint *)const_cast<QRasterBuffer *>(this)->scanLine(y); + QRgb *dest = (QRgb *) target->scanLine(y); + for (int x=0; x<w; ++x) { + QRgb pixel = sourceLine[x]; + int alpha = qAlpha(pixel); + if (!alpha) { + dest[x] = 0; + } else { + dest[x] = (alpha << 24) + | ((255*qRed(pixel)/alpha) << 16) + | ((255*qGreen(pixel)/alpha) << 8) + | ((255*qBlue(pixel)/alpha) << 0); + } + } + } +} + + +class QGradientCache +{ + struct CacheInfo + { + inline CacheInfo(QGradientStops s, int op, QGradient::InterpolationMode mode) : + stops(s), opacity(op), interpolationMode(mode) {} + uint buffer[GRADIENT_STOPTABLE_SIZE]; + QGradientStops stops; + int opacity; + QGradient::InterpolationMode interpolationMode; + }; + + typedef QMultiHash<quint64, CacheInfo> QGradientColorTableHash; + +public: + inline const uint *getBuffer(const QGradient &gradient, int opacity) { + quint64 hash_val = 0; + + QGradientStops stops = gradient.stops(); + for (int i = 0; i < stops.size() && i <= 2; i++) + hash_val += stops[i].second.rgba(); + + QGradientColorTableHash::const_iterator it = cache.constFind(hash_val); + + if (it == cache.constEnd()) + return addCacheElement(hash_val, gradient, opacity); + else { + do { + const CacheInfo &cache_info = it.value(); + if (cache_info.stops == stops && cache_info.opacity == opacity && cache_info.interpolationMode == gradient.interpolationMode()) + return cache_info.buffer; + ++it; + } while (it != cache.constEnd() && it.key() == hash_val); + // an exact match for these stops and opacity was not found, create new cache + return addCacheElement(hash_val, gradient, opacity); + } + } + + inline int paletteSize() const { return GRADIENT_STOPTABLE_SIZE; } +protected: + inline int maxCacheSize() const { return 60; } + inline void generateGradientColorTable(const QGradient& g, + uint *colorTable, + int size, int opacity) const; + uint *addCacheElement(quint64 hash_val, const QGradient &gradient, int opacity) { + if (cache.size() == maxCacheSize()) { + int elem_to_remove = qrand() % maxCacheSize(); + cache.remove(cache.keys()[elem_to_remove]); // may remove more than 1, but OK + } + CacheInfo cache_entry(gradient.stops(), opacity, gradient.interpolationMode()); + generateGradientColorTable(gradient, cache_entry.buffer, paletteSize(), opacity); + return cache.insert(hash_val, cache_entry).value().buffer; + } + + QGradientColorTableHash cache; +}; + +void QGradientCache::generateGradientColorTable(const QGradient& gradient, uint *colorTable, int size, int opacity) const +{ + QGradientStops stops = gradient.stops(); + int stopCount = stops.count(); + Q_ASSERT(stopCount > 0); + + bool colorInterpolation = (gradient.interpolationMode() == QGradient::ColorInterpolation); + + uint current_color = ARGB_COMBINE_ALPHA(stops[0].second.rgba(), opacity); + if (stopCount == 1) { + current_color = PREMUL(current_color); + for (int i = 0; i < size; ++i) + colorTable[i] = current_color; + return; + } + + // The position where the gradient begins and ends + qreal begin_pos = stops[0].first; + qreal end_pos = stops[stopCount-1].first; + + int pos = 0; // The position in the color table. + uint next_color; + + qreal incr = 1 / qreal(size); // the double increment. + qreal dpos = 1.5 * incr; // current position in gradient stop list (0 to 1) + + // Up to first point + colorTable[pos++] = PREMUL(current_color); + while (dpos <= begin_pos) { + colorTable[pos] = colorTable[pos - 1]; + ++pos; + dpos += incr; + } + + int current_stop = 0; // We always interpolate between current and current + 1. + + qreal t; // position between current left and right stops + qreal t_delta; // the t increment per entry in the color table + + if (dpos < end_pos) { + // Gradient area + while (dpos > stops[current_stop+1].first) + ++current_stop; + + if (current_stop != 0) + current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity); + next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity); + + if (colorInterpolation) { + current_color = PREMUL(current_color); + next_color = PREMUL(next_color); + } + + qreal diff = stops[current_stop+1].first - stops[current_stop].first; + qreal c = (diff == 0) ? qreal(0) : 256 / diff; + t = (dpos - stops[current_stop].first) * c; + t_delta = incr * c; + + while (true) { + Q_ASSERT(current_stop < stopCount); + + int dist = qRound(t); + int idist = 256 - dist; + + if (colorInterpolation) + colorTable[pos] = INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist); + else + colorTable[pos] = PREMUL(INTERPOLATE_PIXEL_256(current_color, idist, next_color, dist)); + + ++pos; + dpos += incr; + + if (dpos >= end_pos) + break; + + t += t_delta; + + int skip = 0; + while (dpos > stops[current_stop+skip+1].first) + ++skip; + + if (skip != 0) { + current_stop += skip; + if (skip == 1) + current_color = next_color; + else + current_color = ARGB_COMBINE_ALPHA(stops[current_stop].second.rgba(), opacity); + next_color = ARGB_COMBINE_ALPHA(stops[current_stop+1].second.rgba(), opacity); + + if (colorInterpolation) { + if (skip != 1) + current_color = PREMUL(current_color); + next_color = PREMUL(next_color); + } + + qreal diff = stops[current_stop+1].first - stops[current_stop].first; + qreal c = (diff == 0) ? qreal(0) : 256 / diff; + t = (dpos - stops[current_stop].first) * c; + t_delta = incr * c; + } + } + } + + // After last point + current_color = PREMUL(ARGB_COMBINE_ALPHA(stops[stopCount - 1].second.rgba(), opacity)); + while (pos < size - 1) { + colorTable[pos] = current_color; + ++pos; + } + + // Make sure the last color stop is represented at the end of the table + colorTable[size - 1] = current_color; +} + +Q_GLOBAL_STATIC(QGradientCache, qt_gradient_cache) + + +void QSpanData::init(QRasterBuffer *rb, const QRasterPaintEngine *pe) +{ + rasterBuffer = rb; +#ifdef Q_WS_QWS + rasterEngine = const_cast<QRasterPaintEngine *>(pe); +#endif + type = None; + txop = 0; + bilinear = false; + m11 = m22 = m33 = 1.; + m12 = m13 = m21 = m23 = dx = dy = 0.0; + clip = pe ? pe->d_func()->clip() : 0; +} + +extern QImage qt_imageForBrush(int brushStyle, bool invert); + +void QSpanData::setup(const QBrush &brush, int alpha) +{ + Qt::BrushStyle brushStyle = qbrush_style(brush); + switch (brushStyle) { + case Qt::SolidPattern: { + type = Solid; + QColor c = qbrush_color(brush); + solid.color = PREMUL(ARGB_COMBINE_ALPHA(c.rgba(), alpha)); + break; + } + + case Qt::LinearGradientPattern: + { + type = LinearGradient; + const QLinearGradient *g = static_cast<const QLinearGradient *>(brush.gradient()); + gradient.alphaColor = !brush.isOpaque() || alpha != 256; + gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha)); + gradient.spread = g->spread(); + + QLinearGradientData &linearData = gradient.linear; + + linearData.origin.x = g->start().x(); + linearData.origin.y = g->start().y(); + linearData.end.x = g->finalStop().x(); + linearData.end.y = g->finalStop().y(); + break; + } + + case Qt::RadialGradientPattern: + { + type = RadialGradient; + const QRadialGradient *g = static_cast<const QRadialGradient *>(brush.gradient()); + gradient.alphaColor = !brush.isOpaque() || alpha != 256; + gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha)); + gradient.spread = g->spread(); + + QRadialGradientData &radialData = gradient.radial; + + QPointF center = g->center(); + radialData.center.x = center.x(); + radialData.center.y = center.y(); + QPointF focal = g->focalPoint(); + radialData.focal.x = focal.x(); + radialData.focal.y = focal.y(); + radialData.radius = g->radius(); + } + break; + + case Qt::ConicalGradientPattern: + { + type = ConicalGradient; + const QConicalGradient *g = static_cast<const QConicalGradient *>(brush.gradient()); + gradient.alphaColor = !brush.isOpaque() || alpha != 256; + gradient.colorTable = const_cast<uint*>(qt_gradient_cache()->getBuffer(*g, alpha)); + gradient.spread = QGradient::RepeatSpread; + + QConicalGradientData &conicalData = gradient.conical; + + QPointF center = g->center(); + conicalData.center.x = center.x(); + conicalData.center.y = center.y(); + conicalData.angle = g->angle() * 2 * Q_PI / 360.0; + } + break; + + case Qt::Dense1Pattern: + case Qt::Dense2Pattern: + case Qt::Dense3Pattern: + case Qt::Dense4Pattern: + case Qt::Dense5Pattern: + case Qt::Dense6Pattern: + case Qt::Dense7Pattern: + case Qt::HorPattern: + case Qt::VerPattern: + case Qt::CrossPattern: + case Qt::BDiagPattern: + case Qt::FDiagPattern: + case Qt::DiagCrossPattern: + type = Texture; + if (!tempImage) + tempImage = new QImage(); + *tempImage = rasterBuffer->colorizeBitmap(qt_imageForBrush(brushStyle, true), brush.color()); + initTexture(tempImage, alpha, QTextureData::Tiled); + break; + case Qt::TexturePattern: + type = Texture; + if (!tempImage) + tempImage = new QImage(); + + if (qHasPixmapTexture(brush) && brush.texture().isQBitmap()) + *tempImage = rasterBuffer->colorizeBitmap(brush.textureImage(), brush.color()); + else + *tempImage = brush.textureImage(); + initTexture(tempImage, alpha, QTextureData::Tiled, tempImage->rect()); + break; + + case Qt::NoBrush: + default: + type = None; + break; + } + adjustSpanMethods(); +} + +void QSpanData::adjustSpanMethods() +{ + bitmapBlit = 0; + alphamapBlit = 0; + alphaRGBBlit = 0; + + fillRect = 0; + + switch(type) { + case None: + unclipped_blend = 0; + break; + case Solid: + unclipped_blend = rasterBuffer->drawHelper->blendColor; + bitmapBlit = rasterBuffer->drawHelper->bitmapBlit; + alphamapBlit = rasterBuffer->drawHelper->alphamapBlit; + alphaRGBBlit = rasterBuffer->drawHelper->alphaRGBBlit; + fillRect = rasterBuffer->drawHelper->fillRect; + break; + case LinearGradient: + case RadialGradient: + case ConicalGradient: + unclipped_blend = rasterBuffer->drawHelper->blendGradient; + break; + case Texture: +#ifdef Q_WS_QWS +#ifndef QT_NO_RASTERCALLBACKS + if (!rasterBuffer->buffer()) + unclipped_blend = qBlendTextureCallback; + else +#endif + unclipped_blend = qBlendTexture; +#else + unclipped_blend = qBlendTexture; +#endif + break; + } + // setup clipping + if (!unclipped_blend) { + blend = 0; + } else if (!clip) { + blend = unclipped_blend; + } else if (clip->hasRectClip) { + blend = clip->clipRect.isEmpty() ? 0 : qt_span_fill_clipRect; + } else if (clip->hasRegionClip) { + blend = clip->clipRegion.isEmpty() ? 0 : qt_span_fill_clipRegion; + } else { + blend = qt_span_fill_clipped; + } +} + +void QSpanData::setupMatrix(const QTransform &matrix, int bilin) +{ + QTransform inv = matrix.inverted(); + m11 = inv.m11(); + m12 = inv.m12(); + m13 = inv.m13(); + m21 = inv.m21(); + m22 = inv.m22(); + m23 = inv.m23(); + m33 = inv.m33(); + dx = inv.dx(); + dy = inv.dy(); + txop = inv.type(); + bilinear = bilin; + + const bool affine = !m13 && !m23; + fast_matrix = affine + && m11 * m11 + m21 * m21 < 1e4 + && m12 * m12 + m22 * m22 < 1e4 + && qAbs(dx) < 1e4 + && qAbs(dy) < 1e4; + + adjustSpanMethods(); +} + +extern const QVector<QRgb> *qt_image_colortable(const QImage &image); + +void QSpanData::initTexture(const QImage *image, int alpha, QTextureData::Type _type, const QRect &sourceRect) +{ + const QImageData *d = const_cast<QImage *>(image)->data_ptr(); + if (!d || d->height == 0) { + texture.imageData = 0; + texture.width = 0; + texture.height = 0; + texture.x1 = 0; + texture.y1 = 0; + texture.x2 = 0; + texture.y2 = 0; + texture.bytesPerLine = 0; + texture.format = QImage::Format_Invalid; + texture.colorTable = 0; + texture.hasAlpha = alpha != 256; + } else { + texture.imageData = d->data; + texture.width = d->width; + texture.height = d->height; + + if (sourceRect.isNull()) { + texture.x1 = 0; + texture.y1 = 0; + texture.x2 = texture.width; + texture.y2 = texture.height; + } else { + texture.x1 = sourceRect.x(); + texture.y1 = sourceRect.y(); + texture.x2 = qMin(texture.x1 + sourceRect.width(), d->width); + texture.y2 = qMin(texture.y1 + sourceRect.height(), d->height); + } + + texture.bytesPerLine = d->bytes_per_line; + + texture.format = d->format; + texture.colorTable = (d->format <= QImage::Format_Indexed8 && !d->colortable.isEmpty()) ? &d->colortable : 0; + texture.hasAlpha = image->hasAlphaChannel() || alpha != 256; + } + texture.const_alpha = alpha; + texture.type = _type; + + adjustSpanMethods(); +} + +#ifdef Q_WS_WIN + + +#endif + + +/*! + \internal + + Draws a line using the floating point midpoint algorithm. The line + \a line is already in device coords at this point. +*/ + +static void drawLine_midpoint_i(int x1, int y1, int x2, int y2, ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &devRect) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - drawLine_midpoint_i" << QLine(QPoint(x1, y1), QPoint(x2, y2)); +#endif + + int x, y; + int dx, dy, d, incrE, incrNE; + + dx = x2 - x1; + dy = y2 - y1; + + const int NSPANS = 256; + QT_FT_Span spans[NSPANS]; + int current = 0; + bool ordered = true; + + if (dy == 0) { + // specialcase horizontal lines + if (y1 >= devRect.y1 && y1 < devRect.y2) { + int start = qMax(devRect.x1, qMin(x1, x2)); + int stop = qMax(x1, x2) + 1; + int stop_clipped = qMin(devRect.x2, stop); + int len = stop_clipped - start; + if (style == LineDrawNormal && stop == stop_clipped) + len--; + if (len > 0) { + spans[0].x = ushort(start); + spans[0].len = ushort(len); + spans[0].y = y1; + spans[0].coverage = 255; + span_func(1, spans, data); + } + } + return; + } else if (dx == 0) { + // specialcase vertical lines + if (x1 >= devRect.x1 && x1 < devRect.x2) { + int start = qMax(devRect.y1, qMin(y1, y2)); + int stop = qMax(y1, y2) + 1; + int stop_clipped = qMin(devRect.y2, stop); + int len = stop_clipped - start; + if (style == LineDrawNormal && stop == stop_clipped) + len--; + // hw: create spans directly instead to possibly avoid clipping + if (len > 0) + fillRect_normalized(QRect(x1, start, 1, len).normalized(), data, 0); + } + return; + } + + + if (qAbs(dx) >= qAbs(dy)) { /* if x is the major axis: */ + + if (x2 < x1) { /* if coordinates are out of order */ + qt_swap_int(x1, x2); + dx = -dx; + + qt_swap_int(y1, y2); + dy = -dy; + } + + if (style == LineDrawNormal) + --x2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + x2 = qMin(x2, devRect.x2 - 1); + + // completely clipped, so abort + if (x2 <= x1) { + return; + } + + int x = x1; + int y = y1; + + if (y2 <= y1) + ordered = false; + + { + const int index = (ordered ? current : NSPANS - 1 - current); + spans[index].coverage = 255; + spans[index].x = x; + spans[index].y = y; + + if (x >= devRect.x1 && y >= devRect.y1 && y < devRect.y2) + spans[index].len = 1; + else + spans[index].len = 0; + } + + if (y2 > y1) { // 315 -> 360 and 135 -> 180 (unit circle degrees) + y2 = qMin(y2, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE - dx; + incrNE = (dy - dx) * 2; + + if (y > y2) + goto flush_and_return; + + while (x < x2) { + ++x; + if (d > 0) { + if (spans[current].len > 0) + ++current; + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + + ++y; + d += incrNE; + if (y > y2) + goto flush_and_return; + + spans[current].len = 0; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + } else { + d += incrE; + if (x == devRect.x1) + spans[current].x = devRect.x1; + } + + if (x < devRect.x1 || y < devRect.y1) + continue; + + Q_ASSERT(x<devRect.x2); + Q_ASSERT(y<devRect.y2); + Q_ASSERT(spans[current].y == y); + spans[current].len++; + } + if (spans[current].len > 0) { + ++current; + } + } else { // 0-45 and 180->225 (unit circle degrees) + + y1 = qMin(y1, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE + dx; + incrNE = (dy + dx) * 2; + + if (y < devRect.y1) + goto flush_and_return; + + while (x < x2) { + ++x; + if (d < 0) { + if (spans[NSPANS - 1 - current].len > 0) + ++current; + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + + --y; + d += incrNE; + if (y < devRect.y1) + goto flush_and_return; + + const int index = NSPANS - 1 - current; + spans[index].len = 0; + spans[index].coverage = 255; + spans[index].x = x; + spans[index].y = y; + } else { + d += incrE; + if (x == devRect.x1) + spans[NSPANS - 1 - current].x = devRect.x1; + } + + if (x < devRect.x1 || y > y1) + continue; + + Q_ASSERT(x<devRect.x2 && y<devRect.y2); + Q_ASSERT(spans[NSPANS - 1 - current].y == y); + spans[NSPANS - 1 - current].len++; + } + if (spans[NSPANS - 1 - current].len > 0) { + ++current; + } + } + + } else { + + // if y is the major axis: + + if (y2 < y1) { /* if coordinates are out of order */ + qt_swap_int(y1, y2); + dy = -dy; + + qt_swap_int(x1, x2); + dx = -dx; + } + + if (style == LineDrawNormal) + --y2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + y2 = qMin(y2, devRect.y2 - 1); + + // completely clipped, so abort + if (y2 <= y1) { + return; + } + + x = x1; + y = y1; + + if (x>=devRect.x1 && y>=devRect.y1 && x < devRect.x2) { + Q_ASSERT(x >= devRect.x1 && y >= devRect.y1 && x < devRect.x2 && y < devRect.y2); + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + + if (x2 > x1) { // 90 -> 135 and 270 -> 315 (unit circle degrees) + x2 = qMin(x2, devRect.x2 - 1); + incrE = dx * 2; + d = incrE - dy; + incrNE = (dx - dy) * 2; + + if (x > x2) + goto flush_and_return; + + while (y < y2) { + if (d > 0) { + ++x; + d += incrNE; + if (x > x2) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + if (x < devRect.x1 || y < devRect.y1) + continue; + Q_ASSERT(x<devRect.x2 && y<devRect.y2); + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + } else { // 45 -> 90 and 225 -> 270 (unit circle degrees) + x1 = qMin(x1, devRect.x2 - 1); + incrE = dx * 2; + d = incrE + dy; + incrNE = (dx + dy) * 2; + + if (x < devRect.x1) + goto flush_and_return; + + while (y < y2) { + if (d < 0) { + --x; + d += incrNE; + if (x < devRect.x1) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + if (y < devRect.y1 || x > x1) + continue; + Q_ASSERT(x>=devRect.x1 && x<devRect.x2 && y>=devRect.y1 && y<devRect.y2); + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + } + } +flush_and_return: + if (current > 0) + span_func(current, ordered ? spans : spans + (NSPANS - current), data); +} + +static void offset_pattern(int offset, bool *inDash, int *dashIndex, int *currentOffset, const QVarLengthArray<qreal> &pattern) +{ + while (offset--) { + if (--*currentOffset == 0) { + *inDash = !*inDash; + *dashIndex = ((*dashIndex + 1) % pattern.size()); + *currentOffset = int(pattern[*dashIndex]); + } + } +} + +static void drawLine_midpoint_dashed_i(int x1, int y1, int x2, int y2, + QPen *pen, + ProcessSpans span_func, QSpanData *data, + LineDrawMode style, const QIntRect &devRect, + int *patternOffset) +{ +#ifdef QT_DEBUG_DRAW + qDebug() << " - drawLine_midpoint_dashed_i" << x1 << y1 << x2 << y2 << *patternOffset; +#endif + + int x, y; + int dx, dy, d, incrE, incrNE; + + dx = x2 - x1; + dy = y2 - y1; + + Q_ASSERT(*patternOffset >= 0); + + const QVector<qreal> penPattern = pen->dashPattern(); + QVarLengthArray<qreal> pattern(penPattern.size()); + + int patternLength = 0; + for (int i = 0; i < penPattern.size(); ++i) + patternLength += qMax<qreal>(1.0, (penPattern.at(i))); + + // pattern must be reversed if coordinates are out of order + int reverseLength = -1; + if (dy == 0 && x1 > x2) + reverseLength = x1 - x2; + else if (dx == 0 && y1 > y2) + reverseLength = y1 - y2; + else if (qAbs(dx) >= qAbs(dy) && x2 < x1) // x major axis + reverseLength = qAbs(dx); + else if (qAbs(dy) >= qAbs(dx) && y2 < y1) // y major axis + reverseLength = qAbs(dy); + + const bool reversed = (reverseLength > -1); + if (reversed) { // reverse pattern + for (int i = 0; i < penPattern.size(); ++i) + pattern[penPattern.size() - 1 - i] = qMax<qreal>(1.0, penPattern.at(i)); + + *patternOffset = (patternLength - 1 - *patternOffset); + *patternOffset += patternLength - (reverseLength % patternLength); + *patternOffset = *patternOffset % patternLength; + } else { + for (int i = 0; i < penPattern.size(); ++i) + pattern[i] = qMax<qreal>(1.0, penPattern.at(i)); + } + + int dashIndex = 0; + bool inDash = !reversed; + int currPattern = int(pattern[dashIndex]); + + // adjust pattern for offset + offset_pattern(*patternOffset, &inDash, &dashIndex, &currPattern, pattern); + + const int NSPANS = 256; + QT_FT_Span spans[NSPANS]; + int current = 0; + bool ordered = true; + + if (dy == 0) { + // specialcase horizontal lines + if (y1 >= devRect.y1 && y1 < devRect.y2) { + int start_unclipped = qMin(x1, x2); + int start = qMax(devRect.x1, start_unclipped); + int stop = qMax(x1, x2) + 1; + int stop_clipped = qMin(devRect.x2, stop); + int len = stop_clipped - start; + if (style == LineDrawNormal && stop == stop_clipped) + len--; + + // adjust pattern for starting offset + offset_pattern(start - start_unclipped, &inDash, &dashIndex, &currPattern, pattern); + + if (len > 0) { + int x = start; + while (x < stop_clipped) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + const int dash = qMin(currPattern, stop_clipped - x); + if (inDash) { + spans[current].x = ushort(x); + spans[current].len = ushort(dash); + spans[current].y = y1; + spans[current].coverage = 255; + ++current; + } + if (dash < currPattern) { + currPattern -= dash; + } else { + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + inDash = !inDash; + } + x += dash; + } + } + } + goto flush_and_return; + } else if (dx == 0) { + if (x1 >= devRect.x1 && x1 < devRect.x2) { + int start_unclipped = qMin(y1, y2); + int start = qMax(devRect.y1, start_unclipped); + int stop = qMax(y1, y2) + 1; + int stop_clipped = qMin(devRect.y2, stop); + if (style == LineDrawNormal && stop == stop_clipped) + --stop; + else + stop = stop_clipped; + + // adjust pattern for starting offset + offset_pattern(start - start_unclipped, &inDash, &dashIndex, &currPattern, pattern); + + // loop over dashes + int y = start; + while (y < stop) { + const int dash = qMin(currPattern, stop - y); + if (inDash) { + for (int i = 0; i < dash; ++i) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].x = x1; + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].y = ushort(y + i); + ++current; + } + } + if (dash < currPattern) { + currPattern -= dash; + } else { + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + inDash = !inDash; + } + y += dash; + } + } + goto flush_and_return; + } + + if (qAbs(dx) >= qAbs(dy)) { /* if x is the major axis: */ + + if (x2 < x1) { /* if coordinates are out of order */ + qt_swap_int(x1, x2); + dx = -dx; + + qt_swap_int(y1, y2); + dy = -dy; + } + + if (style == LineDrawNormal) + --x2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + x2 = qMin(x2, devRect.x2 - 1); + + // completely clipped, so abort + if (x2 <= x1) + goto flush_and_return; + + int x = x1; + int y = y1; + + if (x >= devRect.x1 && y >= devRect.y1 && y < devRect.y2) { + Q_ASSERT(x < devRect.x2); + if (inDash) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + + if (y2 > y1) { // 315 -> 360 and 135 -> 180 (unit circle degrees) + y2 = qMin(y2, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE - dx; + incrNE = (dy - dx) * 2; + + if (y > y2) + goto flush_and_return; + + while (x < x2) { + if (d > 0) { + ++y; + d += incrNE; + if (y > y2) + goto flush_and_return; + } else { + d += incrE; + } + ++x; + + const bool skip = x < devRect.x1 || y < devRect.y1; + Q_ASSERT(skip || (x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } else { // 0-45 and 180->225 (unit circle degrees) + y1 = qMin(y1, devRect.y2 - 1); + + incrE = dy * 2; + d = incrE + dx; + incrNE = (dy + dx) * 2; + + if (y < devRect.y1) + goto flush_and_return; + + while (x < x2) { + if (d < 0) { + if (current > 0) { + span_func(current, spans, data); + current = 0; + } + + --y; + d += incrNE; + if (y < devRect.y1) + goto flush_and_return; + } else { + d += incrE; + } + ++x; + + const bool skip = x < devRect.x1 || y > y1; + Q_ASSERT(skip || (x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } + } else { + + // if y is the major axis: + + if (y2 < y1) { /* if coordinates are out of order */ + qt_swap_int(y1, y2); + dy = -dy; + + qt_swap_int(x1, x2); + dx = -dx; + } + + if (style == LineDrawNormal) + --y2; + + // In the loops below we increment before call the span function so + // we need to stop one pixel before + y2 = qMin(y2, devRect.y2 - 1); + + // completely clipped, so abort + if (y2 <= y1) + goto flush_and_return; + + x = x1; + y = y1; + + if (x>=devRect.x1 && y>=devRect.y1 && x < devRect.x2) { + Q_ASSERT(x < devRect.x2); + if (inDash) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + + if (x2 > x1) { // 90 -> 135 and 270 -> 315 (unit circle degrees) + x2 = qMin(x2, devRect.x2 - 1); + incrE = dx * 2; + d = incrE - dy; + incrNE = (dx - dy) * 2; + + if (x > x2) + goto flush_and_return; + + while (y < y2) { + if (d > 0) { + ++x; + d += incrNE; + if (x > x2) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + const bool skip = x < devRect.x1 || y < devRect.y1; + Q_ASSERT(skip || (x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } else { // 45 -> 90 and 225 -> 270 (unit circle degrees) + x1 = qMin(x1, devRect.x2 - 1); + incrE = dx * 2; + d = incrE + dy; + incrNE = (dx + dy) * 2; + + if (x < devRect.x1) + goto flush_and_return; + + while (y < y2) { + if (d < 0) { + --x; + d += incrNE; + if (x < devRect.x1) + goto flush_and_return; + } else { + d += incrE; + } + ++y; + const bool skip = y < devRect.y1 || x > x1; + Q_ASSERT(skip || (x >= devRect.x1 && x < devRect.x2 && y < devRect.y2)); + if (inDash && !skip) { + if (current == NSPANS) { + span_func(NSPANS, spans, data); + current = 0; + } + spans[current].len = 1; + spans[current].coverage = 255; + spans[current].x = x; + spans[current].y = y; + ++current; + } + if (--currPattern <= 0) { + inDash = !inDash; + dashIndex = (dashIndex + 1) % pattern.size(); + currPattern = int(pattern[dashIndex]); + } + } + } + } +flush_and_return: + if (current > 0) + span_func(current, ordered ? spans : spans + (NSPANS - current), data); + + // adjust offset + if (reversed) { + *patternOffset = (patternLength - 1 - *patternOffset); + } else { + *patternOffset = 0; + for (int i = 0; i <= dashIndex; ++i) + *patternOffset += int(pattern[i]); + *patternOffset += patternLength - currPattern - 1; + *patternOffset = (*patternOffset % patternLength); + } +} + +/*! + \internal + \a x and \a y is relative to the midpoint of \a rect. +*/ +static inline void drawEllipsePoints(int x, int y, int length, + const QRect &rect, + const QRect &clip, + ProcessSpans pen_func, ProcessSpans brush_func, + QSpanData *pen_data, QSpanData *brush_data) +{ + if (length == 0) + return; + + QT_FT_Span outline[4]; + const int midx = rect.x() + (rect.width() + 1) / 2; + const int midy = rect.y() + (rect.height() + 1) / 2; + + x = x + midx; + y = midy - y; + + // topleft + outline[0].x = midx + (midx - x) - (length - 1) - (rect.width() & 0x1); + outline[0].len = qMin(length, x - outline[0].x); + outline[0].y = y; + outline[0].coverage = 255; + + // topright + outline[1].x = x; + outline[1].len = length; + outline[1].y = y; + outline[1].coverage = 255; + + // bottomleft + outline[2].x = outline[0].x; + outline[2].len = outline[0].len; + outline[2].y = midy + (midy - y) - (rect.height() & 0x1); + outline[2].coverage = 255; + + // bottomright + outline[3].x = x; + outline[3].len = length; + outline[3].y = outline[2].y; + outline[3].coverage = 255; + + if (brush_func && outline[0].x + outline[0].len < outline[1].x) { + QT_FT_Span fill[2]; + + // top fill + fill[0].x = outline[0].x + outline[0].len - 1; + fill[0].len = qMax(0, outline[1].x - fill[0].x); + fill[0].y = outline[1].y; + fill[0].coverage = 255; + + // bottom fill + fill[1].x = outline[2].x + outline[2].len - 1; + fill[1].len = qMax(0, outline[3].x - fill[1].x); + fill[1].y = outline[3].y; + fill[1].coverage = 255; + + int n = (fill[0].y >= fill[1].y ? 1 : 2); + n = qt_intersect_spans(fill, n, clip); + if (n > 0) + brush_func(n, fill, brush_data); + } + if (pen_func) { + int n = (outline[1].y >= outline[2].y ? 2 : 4); + n = qt_intersect_spans(outline, n, clip); + if (n > 0) + pen_func(n, outline, pen_data); + } +} + +/*! + \internal + Draws an ellipse using the integer point midpoint algorithm. +*/ +static void drawEllipse_midpoint_i(const QRect &rect, const QRect &clip, + ProcessSpans pen_func, ProcessSpans brush_func, + QSpanData *pen_data, QSpanData *brush_data) +{ +#ifdef FLOATING_POINT_BUGGY_OR_NO_FPU // no fpu, so use fixed point + const QFixed a = QFixed(rect.width()) >> 1; + const QFixed b = QFixed(rect.height()) >> 1; + QFixed d = b*b - (a*a*b) + ((a*a) >> 2); +#else + const qreal a = qreal(rect.width()) / 2; + const qreal b = qreal(rect.height()) / 2; + qreal d = b*b - (a*a*b) + 0.25*a*a; +#endif + + int x = 0; + int y = (rect.height() + 1) / 2; + int startx = x; + + // region 1 + while (a*a*(2*y - 1) > 2*b*b*(x + 1)) { + if (d < 0) { // select E + d += b*b*(2*x + 3); + ++x; + } else { // select SE + d += b*b*(2*x + 3) + a*a*(-2*y + 2); + drawEllipsePoints(startx, y, x - startx + 1, rect, clip, + pen_func, brush_func, pen_data, brush_data); + startx = ++x; + --y; + } + } + drawEllipsePoints(startx, y, x - startx + 1, rect, clip, + pen_func, brush_func, pen_data, brush_data); + + // region 2 +#ifdef FLOATING_POINT_BUGGY_OR_NO_FPU + d = b*b*(x + (QFixed(1) >> 1))*(x + (QFixed(1) >> 1)) + + a*a*((y - 1)*(y - 1) - b*b); +#else + d = b*b*(x + 0.5)*(x + 0.5) + a*a*((y - 1)*(y - 1) - b*b); +#endif + const int miny = rect.height() & 0x1; + while (y > miny) { + if (d < 0) { // select SE + d += b*b*(2*x + 2) + a*a*(-2*y + 3); + ++x; + } else { // select S + d += a*a*(-2*y + 3); + } + --y; + drawEllipsePoints(x, y, 1, rect, clip, + pen_func, brush_func, pen_data, brush_data); + } +} + +/*! + \fn void QRasterPaintEngine::drawPoints(const QPoint *points, int pointCount) + \overload + + Draws the first \a pointCount points in the buffer \a points + + The default implementation converts the first \a pointCount QPoints in \a points + to QPointFs and calls the floating point version of drawPoints. +*/ + +/*! + \fn void QRasterPaintEngine::drawEllipse(const QRect &rect) + \overload + + Reimplement this function to draw the largest ellipse that can be + contained within rectangle \a rect. +*/ + +#ifdef QT_DEBUG_DRAW +void dumpClip(int width, int height, QClipData *clip) +{ + QImage clipImg(width, height, QImage::Format_ARGB32_Premultiplied); + clipImg.fill(0xffff0000); + + int x0 = width; + int x1 = 0; + int y0 = height; + int y1 = 0; + + for (int i = 0; i < clip->count; ++i) { + QSpan *span = clip->spans + i; + for (int j = 0; j < span->len; ++j) + clipImg.setPixel(span->x + j, span->y, 0xffffff00); + x0 = qMin(x0, int(span->x)); + x1 = qMax(x1, int(span->x + span->len - 1)); + + y0 = qMin(y0, int(span->y)); + y1 = qMax(y1, int(span->y)); + } + + static int counter = 0; + + Q_ASSERT(y0 >= 0); + Q_ASSERT(x0 >= 0); + Q_ASSERT(y1 >= 0); + Q_ASSERT(x1 >= 0); + + fprintf(stderr,"clip %d: %d %d - %d %d\n", counter, x0, y0, x1, y1); + clipImg.save(QString(QLatin1String("clip-%0.png")).arg(counter++)); +} +#endif + + +QT_END_NAMESPACE |