/* * Copyright (C) 2006 Dirk Mueller * Copyright (C) 2006 Zack Rusin * Copyright (C) 2006 George Staikos * Copyright (C) 2006 Simon Hausmann * Copyright (C) 2006 Allan Sandfeld Jensen * Copyright (C) 2006 Nikolas Zimmermann * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies). * Copyright (C) 2008 Dirk Schulze * Copyright (C) 2010, 2011 Sencha, Inc. * Copyright (C) 2011 Andreas Kling * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "GraphicsContext.h" #if OS(WINDOWS) #include #endif #include "AffineTransform.h" #include "Color.h" #include "FloatConversion.h" #include "Font.h" #include "ImageBuffer.h" #include "NotImplemented.h" #include "Path.h" #include "Pattern.h" #include "ShadowBlur.h" #include "TransparencyLayer.h" #include #include #include #include #include #include #include #include #include #include #include #include #if OS(WINDOWS) Q_GUI_EXPORT QPixmap qt_pixmapFromWinHBITMAP(HBITMAP, int hbitmapFormat = 0); enum HBitmapFormat { HBitmapNoAlpha, HBitmapPremultipliedAlpha, HBitmapAlpha }; #endif namespace WebCore { static inline QPainter::CompositionMode toQtCompositionMode(CompositeOperator op) { switch (op) { case CompositeClear: return QPainter::CompositionMode_Clear; case CompositeCopy: return QPainter::CompositionMode_Source; case CompositeSourceOver: return QPainter::CompositionMode_SourceOver; case CompositeSourceIn: return QPainter::CompositionMode_SourceIn; case CompositeSourceOut: return QPainter::CompositionMode_SourceOut; case CompositeSourceAtop: return QPainter::CompositionMode_SourceAtop; case CompositeDestinationOver: return QPainter::CompositionMode_DestinationOver; case CompositeDestinationIn: return QPainter::CompositionMode_DestinationIn; case CompositeDestinationOut: return QPainter::CompositionMode_DestinationOut; case CompositeDestinationAtop: return QPainter::CompositionMode_DestinationAtop; case CompositeXOR: return QPainter::CompositionMode_Xor; case CompositePlusDarker: // there is no exact match, but this is the closest return QPainter::CompositionMode_Darken; case CompositePlusLighter: return QPainter::CompositionMode_Plus; case CompositeDifference: return QPainter::CompositionMode_Difference; default: ASSERT_NOT_REACHED(); } return QPainter::CompositionMode_SourceOver; } static inline Qt::PenCapStyle toQtLineCap(LineCap lc) { switch (lc) { case ButtCap: return Qt::FlatCap; case RoundCap: return Qt::RoundCap; case SquareCap: return Qt::SquareCap; default: ASSERT_NOT_REACHED(); } return Qt::FlatCap; } static inline Qt::PenJoinStyle toQtLineJoin(LineJoin lj) { switch (lj) { case MiterJoin: return Qt::SvgMiterJoin; case RoundJoin: return Qt::RoundJoin; case BevelJoin: return Qt::BevelJoin; default: ASSERT_NOT_REACHED(); } return Qt::SvgMiterJoin; } static Qt::PenStyle toQPenStyle(StrokeStyle style) { switch (style) { case NoStroke: return Qt::NoPen; break; case SolidStroke: return Qt::SolidLine; break; case DottedStroke: return Qt::DotLine; break; case DashedStroke: return Qt::DashLine; break; default: ASSERT_NOT_REACHED(); } return Qt::NoPen; } static inline Qt::FillRule toQtFillRule(WindRule rule) { switch (rule) { case RULE_EVENODD: return Qt::OddEvenFill; case RULE_NONZERO: return Qt::WindingFill; default: ASSERT_NOT_REACHED(); } return Qt::OddEvenFill; } static inline void adjustPointsForDottedLine(FloatPoint& p1, FloatPoint& p2, float width, bool isVerticalLine) { if (isVerticalLine) { p1.setY(p1.y() - width / 2); p2.setY(p2.y() + width / 2); } else { p1.setX(p1.x() - width / 2); p2.setX(p2.x() + width / 2); } } static inline void drawLineEndpointsForStyle(QPainter *painter, const FloatPoint& p1, const FloatPoint& p2, float width, bool isVerticalLine, StrokeStyle style, Color color) { // Do a rect fill of our endpoints. This ensures we always have the // appearance of being a border. if (style == DashedStroke) { if (isVerticalLine) { painter->fillRect(FloatRect(p1.x() - width / 2, p1.y() - width, width, width), QColor(color)); painter->fillRect(FloatRect(p2.x() - width / 2, p2.y(), width, width), QColor(color)); } else { painter->fillRect(FloatRect(p1.x() - width, p1.y() - width / 2, width, width), QColor(color)); painter->fillRect(FloatRect(p2.x(), p2.y() - width / 2, width, width), QColor(color)); } } // As per css spec a dotted stroke should be made of circles so we're // drawing circles as endpoints. if (style == DottedStroke) { painter->setPen(Qt::NoPen); painter->setBrush(QColor(color)); painter->drawEllipse(p1.x() - width / 2, p1.y() - width / 2, width, width); painter->drawEllipse(p2.x() - width / 2, p2.y() - width / 2, width, width); } } class GraphicsContextPlatformPrivate { WTF_MAKE_NONCOPYABLE(GraphicsContextPlatformPrivate); WTF_MAKE_FAST_ALLOCATED; public: GraphicsContextPlatformPrivate(QPainter*, const QColor& initialSolidColor); ~GraphicsContextPlatformPrivate(); inline QPainter* p() const { if (layers.isEmpty()) return painter; return &layers.top()->painter; } bool antiAliasingForRectsAndLines; QStack layers; // Counting real layers. Required by isInTransparencyLayer() calls // For example, layers with valid alphaMask are not real layers int layerCount; // reuse this brush for solid color (to prevent expensive QBrush construction) QBrush solidColor; InterpolationQuality imageInterpolationQuality; bool initialSmoothPixmapTransformHint; ShadowBlur* shadow; QRectF clipBoundingRect() const { return p()->clipBoundingRect(); } void takeOwnershipOfPlatformContext() { platformContextIsOwned = true; } private: QPainter* painter; bool platformContextIsOwned; }; GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate(QPainter* p, const QColor& initialSolidColor) : antiAliasingForRectsAndLines(false) , layerCount(0) , solidColor(initialSolidColor) , imageInterpolationQuality(InterpolationDefault) , initialSmoothPixmapTransformHint(false) , shadow(new ShadowBlur()) , painter(p) , platformContextIsOwned(false) { if (!painter) return; // Use the default the QPainter was constructed with. antiAliasingForRectsAndLines = painter->testRenderHint(QPainter::Antialiasing); // Used for default image interpolation quality. initialSmoothPixmapTransformHint = painter->testRenderHint(QPainter::SmoothPixmapTransform); painter->setRenderHint(QPainter::Antialiasing, true); } GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate() { delete shadow; if (!platformContextIsOwned) return; QPaintDevice* device = painter->device(); painter->end(); delete painter; delete device; } void GraphicsContext::platformInit(PlatformGraphicsContext* painter) { m_data = new GraphicsContextPlatformPrivate(painter, fillColor()); setPaintingDisabled(!painter); if (!painter) return; // solidColor is initialized with the fillColor(). painter->setBrush(m_data->solidColor); QPen pen(painter->pen()); pen.setColor(strokeColor()); pen.setJoinStyle(toQtLineJoin(MiterJoin)); painter->setPen(pen); } void GraphicsContext::platformDestroy() { while (!m_data->layers.isEmpty()) endTransparencyLayer(); delete m_data; } PlatformGraphicsContext* GraphicsContext::platformContext() const { return m_data->p(); } AffineTransform GraphicsContext::getCTM(IncludeDeviceScale) const { if (paintingDisabled()) return AffineTransform(); const QTransform& matrix = platformContext()->combinedTransform(); return AffineTransform(matrix.m11(), matrix.m12(), matrix.m21(), matrix.m22(), matrix.dx(), matrix.dy()); } void GraphicsContext::savePlatformState() { if (!m_data->layers.isEmpty() && !m_data->layers.top()->alphaMask.isNull()) ++m_data->layers.top()->saveCounter; m_data->p()->save(); } void GraphicsContext::restorePlatformState() { if (!m_data->layers.isEmpty() && !m_data->layers.top()->alphaMask.isNull()) if (!--m_data->layers.top()->saveCounter) endPlatformTransparencyLayer(); m_data->p()->restore(); m_data->shadow->setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur), m_state.shadowOffset, m_state.shadowColor, m_state.shadowColorSpace, m_state.shadowsIgnoreTransforms); } // Draws a filled rectangle with a stroked border. // This is only used to draw borders (real fill is done via fillRect), and // thus it must not cast any shadow. void GraphicsContext::drawRect(const IntRect& rect) { if (paintingDisabled()) return; ASSERT(!rect.isEmpty()); QPainter* p = m_data->p(); const bool antiAlias = p->testRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::Antialiasing, m_data->antiAliasingForRectsAndLines); p->drawRect(rect); p->setRenderHint(QPainter::Antialiasing, antiAlias); } // This is only used to draw borders. // Must not cast any shadow. void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2) { if (paintingDisabled()) return; StrokeStyle style = strokeStyle(); Color color = strokeColor(); if (style == NoStroke) return; float width = strokeThickness(); FloatPoint p1 = point1; FloatPoint p2 = point2; bool isVerticalLine = (p1.x() == p2.x()); QPainter* p = m_data->p(); const bool antiAlias = p->testRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::Antialiasing, m_data->antiAliasingForRectsAndLines); adjustLineToPixelBoundaries(p1, p2, width, style); Qt::PenCapStyle capStyle = Qt::FlatCap; QVector dashes; int patWidth = 0; switch (style) { case NoStroke: case SolidStroke: break; case DottedStroke: { capStyle = Qt::RoundCap; patWidth = static_cast(width); // The actual length of one line element can not be set to zero and at 0.1 the dots // are still slightly elongated. Setting it to 0.01 will make it look like the // line endings are being stuck together, close enough to look like a circle. // For the distance of the line elements we subtract the small amount again. const qreal lineElementLength = 0.01; dashes << lineElementLength << qreal(2 * patWidth) / width - lineElementLength; adjustPointsForDottedLine(p1, p2, width, isVerticalLine); break; } case DashedStroke: capStyle = Qt::FlatCap; patWidth = 3 * static_cast(width); dashes << qreal(patWidth) / width << qreal(patWidth) / width; break; } if (patWidth) { p->save(); QPen pen = p->pen(); drawLineEndpointsForStyle(p, p1, p2, width, isVerticalLine, style, color); // Example: 80 pixels with a width of 30 pixels. // Remainder is 20. The maximum pixels of line we could paint // will be 50 pixels. int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width; int remainder = distance % patWidth; int coverage = distance - remainder; int numSegments = coverage / patWidth; float patternOffset = 0.0f; // Special case 1px dotted borders for speed. if (patWidth == 1) patternOffset = 1.0f; else { bool evenNumberOfSegments = !(numSegments % 2); if (remainder) evenNumberOfSegments = !evenNumberOfSegments; if (evenNumberOfSegments) { if (remainder) { patternOffset += patWidth - remainder; patternOffset += remainder / 2; } else patternOffset = patWidth / 2; } else { if (remainder) patternOffset = (patWidth - remainder) / 2; } } pen.setWidthF(width); pen.setCapStyle(capStyle); pen.setDashPattern(dashes); pen.setDashOffset(patternOffset / width); p->setPen(pen); } // Qt interprets geometric units as end-point inclusive, while WebCore interprets geomtric units as endpoint exclusive. // This means we need to subtract one from the endpoint, or the line will be painted one pixel too long. if (p1.x() == p2.x()) p->drawLine(p1, p2 - FloatSize(0, 1)); else p->drawLine(p1, p2 - FloatSize(1, 0)); if (patWidth) p->restore(); p->setRenderHint(QPainter::Antialiasing, antiAlias); } // This method is only used to draw the little circles used in lists. void GraphicsContext::drawEllipse(const IntRect& rect) { if (paintingDisabled()) return; m_data->p()->drawEllipse(rect); } void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias) { if (paintingDisabled()) return; if (npoints <= 1) return; QPolygonF polygon(npoints); for (size_t i = 0; i < npoints; i++) polygon[i] = points[i]; QPainter* p = m_data->p(); const bool antiAlias = p->testRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::Antialiasing, shouldAntialias); p->drawConvexPolygon(polygon); p->setRenderHint(QPainter::Antialiasing, antiAlias); } void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint* points, bool antialiased) { if (paintingDisabled()) return; if (numPoints <= 1) return; QPainterPath path(points[0]); for (size_t i = 1; i < numPoints; ++i) path.lineTo(points[i]); path.setFillRule(Qt::WindingFill); QPainter* p = m_data->p(); bool painterWasAntialiased = p->testRenderHint(QPainter::Antialiasing); if (painterWasAntialiased != antialiased) p->setRenderHint(QPainter::Antialiasing, antialiased); p->setClipPath(path, Qt::IntersectClip); if (painterWasAntialiased != antialiased) p->setRenderHint(QPainter::Antialiasing, painterWasAntialiased); } void GraphicsContext::fillPath(const Path& path) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPainterPath platformPath = path.platformPath(); platformPath.setFillRule(toQtFillRule(fillRule())); if (hasShadow()) { ShadowBlur* shadow = shadowBlur(); if (shadow->mustUseShadowBlur(this) || m_state.fillPattern || m_state.fillGradient) { GraphicsContext* shadowContext = shadow->beginShadowLayer(this, platformPath.controlPointRect()); if (shadowContext) { QPainter* shadowPainter = shadowContext->platformContext(); if (m_state.fillPattern) { shadowPainter->fillPath(platformPath, QBrush(m_state.fillPattern->createPlatformPattern())); } else if (m_state.fillGradient) { QBrush brush(*m_state.fillGradient->platformGradient()); brush.setTransform(m_state.fillGradient->gradientSpaceTransform()); shadowPainter->fillPath(platformPath, brush); } else { shadowPainter->fillPath(platformPath, p->brush()); } shadow->endShadowLayer(this); } } else { QPointF offset(m_state.shadowOffset.width(), m_state.shadowOffset.height()); p->translate(offset); QColor shadowColor = m_state.shadowColor; shadowColor.setAlphaF(shadowColor.alphaF() * p->brush().color().alphaF()); p->fillPath(platformPath, shadowColor); p->translate(-offset); } } if (m_state.fillPattern) { p->fillPath(platformPath, QBrush(m_state.fillPattern->createPlatformPattern())); } else if (m_state.fillGradient) { QBrush brush(*m_state.fillGradient->platformGradient()); brush.setTransform(m_state.fillGradient->gradientSpaceTransform()); p->fillPath(platformPath, brush); } else p->fillPath(platformPath, p->brush()); } inline static void fillPathStroke(QPainter* painter, QPainterPathStroker& pathStroker, const QPainterPath& platformPath, const QBrush& brush) { QPainterPath stroke = pathStroker.createStroke(platformPath); painter->fillPath(stroke, brush); } void GraphicsContext::strokePath(const Path& path) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPen pen(p->pen()); QPainterPath platformPath = path.platformPath(); platformPath.setFillRule(toQtFillRule(fillRule())); QPainterPathStroker pathStroker; pathStroker.setJoinStyle(pen.joinStyle()); pathStroker.setDashOffset(pen.dashOffset()); pathStroker.setMiterLimit(pen.miterLimit()); pathStroker.setCapStyle(pen.capStyle()); pathStroker.setWidth(pen.widthF()); if (hasShadow()) { ShadowBlur* shadow = shadowBlur(); if (shadow->mustUseShadowBlur(this) || m_state.strokePattern || m_state.strokeGradient) { FloatRect boundingRect = platformPath.controlPointRect(); boundingRect.inflate(pen.miterLimit() + pen.widthF()); GraphicsContext* shadowContext = shadow->beginShadowLayer(this, boundingRect); if (shadowContext) { QPainter* shadowPainter = shadowContext->platformContext(); if (m_state.strokeGradient) { QBrush brush(*m_state.strokeGradient->platformGradient()); brush.setTransform(m_state.strokeGradient->gradientSpaceTransform()); fillPathStroke(shadowPainter, pathStroker, platformPath, brush); } else fillPathStroke(shadowPainter, pathStroker, platformPath, pen.brush()); shadow->endShadowLayer(this); } } else { QPointF offset(m_state.shadowOffset.width(), m_state.shadowOffset.height()); p->translate(offset); QColor shadowColor = m_state.shadowColor; shadowColor.setAlphaF(shadowColor.alphaF() * pen.color().alphaF()); QPen shadowPen(pen); shadowPen.setColor(shadowColor); fillPathStroke(p, pathStroker, platformPath, shadowPen.brush()); p->translate(-offset); } } if (m_state.strokePattern) { QBrush brush = m_state.strokePattern->createPlatformPattern(); fillPathStroke(p, pathStroker, platformPath, brush); } else if (m_state.strokeGradient) { QBrush brush(*m_state.strokeGradient->platformGradient()); brush.setTransform(m_state.strokeGradient->gradientSpaceTransform()); fillPathStroke(p, pathStroker, platformPath, brush); } else fillPathStroke(p, pathStroker, platformPath, pen.brush()); } static inline void drawRepeatPattern(QPainter* p, PassRefPtr pattern, const FloatRect& rect) { ASSERT(pattern); const QBrush brush = pattern->createPlatformPattern(); if (brush.style() != Qt::TexturePattern) return; const bool repeatX = pattern->repeatX(); const bool repeatY = pattern->repeatY(); // Patterns must be painted so that the top left of the first image is anchored at // the origin of the coordinate space QRectF targetRect(rect); const int w = brush.texture().width(); const int h = brush.texture().height(); ASSERT(p); QRegion oldClip; if (p->hasClipping()) oldClip = p->clipRegion(); // The only type of transforms supported for the brush are translations. ASSERT(!brush.transform().isRotating()); QRectF clip = targetRect; QRectF patternRect = brush.transform().mapRect(QRectF(0, 0, w, h)); if (!repeatX) { clip.setLeft(patternRect.left()); clip.setWidth(patternRect.width()); } if (!repeatY) { clip.setTop(patternRect.top()); clip.setHeight(patternRect.height()); } if (!repeatX || !repeatY) p->setClipRect(clip); p->fillRect(targetRect, brush); if (!oldClip.isEmpty()) p->setClipRegion(oldClip); else if (!repeatX || !repeatY) p->setClipping(false); } void GraphicsContext::fillRect(const FloatRect& rect) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QRectF normalizedRect = rect.normalized(); ShadowBlur* shadow = shadowBlur(); if (m_state.fillPattern) { GraphicsContext* shadowContext = hasShadow() ? shadow->beginShadowLayer(this, normalizedRect) : 0; if (shadowContext) { QPainter* shadowPainter = shadowContext->platformContext(); drawRepeatPattern(shadowPainter, m_state.fillPattern, normalizedRect); shadow->endShadowLayer(this); } drawRepeatPattern(p, m_state.fillPattern, normalizedRect); } else if (m_state.fillGradient) { QBrush brush(*m_state.fillGradient->platformGradient()); brush.setTransform(m_state.fillGradient->gradientSpaceTransform()); GraphicsContext* shadowContext = hasShadow() ? shadow->beginShadowLayer(this, normalizedRect) : 0; if (shadowContext) { QPainter* shadowPainter = shadowContext->platformContext(); shadowPainter->fillRect(normalizedRect, brush); shadow->endShadowLayer(this); } p->fillRect(normalizedRect, brush); } else { if (hasShadow()) { if (shadow->mustUseShadowBlur(this)) { GraphicsContext* shadowContext = shadow->beginShadowLayer(this, normalizedRect); if (shadowContext) { QPainter* shadowPainter = shadowContext->platformContext(); shadowPainter->fillRect(normalizedRect, p->brush()); shadow->endShadowLayer(this); } } else { // Solid rectangle fill with no blur shadow or transformations applied can be done // faster without using the shadow layer at all. QColor shadowColor = m_state.shadowColor; shadowColor.setAlphaF(shadowColor.alphaF() * p->brush().color().alphaF()); p->fillRect(normalizedRect.translated(QPointF(m_state.shadowOffset.width(), m_state.shadowOffset.height())), shadowColor); } } p->fillRect(normalizedRect, p->brush()); } } void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace) { if (paintingDisabled() || !color.isValid()) return; QRectF platformRect(rect); QPainter* p = m_data->p(); if (hasShadow()) { ShadowBlur* shadow = shadowBlur(); if (shadow->mustUseShadowBlur(this)) { shadow->drawRectShadow(this, platformRect, RoundedRect::Radii()); } else { QColor shadowColor = m_state.shadowColor; shadowColor.setAlphaF(shadowColor.alphaF() * p->brush().color().alphaF()); p->fillRect(platformRect.translated(QPointF(m_state.shadowOffset.width(), m_state.shadowOffset.height())), shadowColor); } } p->fillRect(platformRect, QColor(color)); } void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace) { if (paintingDisabled() || !color.isValid()) return; Path path; path.addRoundedRect(rect, topLeft, topRight, bottomLeft, bottomRight); QPainter* p = m_data->p(); if (hasShadow()) { ShadowBlur* shadow = shadowBlur(); if (shadow->mustUseShadowBlur(this)) { shadow->drawRectShadow(this, rect, RoundedRect::Radii(topLeft, topRight, bottomLeft, bottomRight)); } else { const QPointF shadowOffset(m_state.shadowOffset.width(), m_state.shadowOffset.height()); p->translate(shadowOffset); p->fillPath(path.platformPath(), QColor(m_state.shadowColor)); p->translate(-shadowOffset); } } p->fillPath(path.platformPath(), QColor(color)); } bool GraphicsContext::isInTransparencyLayer() const { return m_data->layerCount; } ShadowBlur* GraphicsContext::shadowBlur() { return m_data->shadow; } void GraphicsContext::clip(const IntRect& rect) { if (paintingDisabled()) return; m_data->p()->setClipRect(rect, Qt::IntersectClip); } void GraphicsContext::clip(const FloatRect& rect) { if (paintingDisabled()) return; m_data->p()->setClipRect(rect, Qt::IntersectClip); } IntRect GraphicsContext::clipBounds() const { QPainter* p = m_data->p(); QRectF clipRect; if (p->hasClipping()) clipRect = m_data->clipBoundingRect(); else clipRect = p->transform().inverted().mapRect(p->window()); return enclosingIntRect(clipRect); } void GraphicsContext::clipPath(const Path& path, WindRule clipRule) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPainterPath platformPath = path.platformPath(); platformPath.setFillRule(clipRule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill); p->setClipPath(platformPath, Qt::IntersectClip); } void drawFocusRingForPath(QPainter* p, const QPainterPath& path, const Color& color, bool antiAliasing) { const bool antiAlias = p->testRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::Antialiasing, antiAliasing); const QPen oldPen = p->pen(); const QBrush oldBrush = p->brush(); QPen nPen = p->pen(); nPen.setColor(color); p->setBrush(Qt::NoBrush); nPen.setStyle(Qt::DotLine); p->strokePath(path, nPen); p->setBrush(oldBrush); p->setPen(oldPen); p->setRenderHint(QPainter::Antialiasing, antiAlias); } void GraphicsContext::drawFocusRing(const Path& path, int /* width */, int offset, const Color& color) { // FIXME: Use 'offset' for something? http://webkit.org/b/49909 if (paintingDisabled() || !color.isValid()) return; drawFocusRingForPath(m_data->p(), path.platformPath(), color, m_data->antiAliasingForRectsAndLines); } /** * Focus ring handling for form controls is not handled here. Qt style in * RenderTheme handles drawing focus on widgets which * need it. It is still handled here for links. */ void GraphicsContext::drawFocusRing(const Vector& rects, int width, int offset, const Color& color) { if (paintingDisabled() || !color.isValid()) return; unsigned rectCount = rects.size(); if (!rects.size()) return; int radius = (width - 1) / 2; QPainterPath path; for (unsigned i = 0; i < rectCount; ++i) { QRect rect = QRect((rects[i])).adjusted(-offset - radius, -offset - radius, offset + radius, offset + radius); // This is not the most efficient way to add a rect to a path, but if we don't create the tmpPath, // we will end up with ugly lines in between rows of text on anchors with multiple lines. QPainterPath tmpPath; tmpPath.addRoundedRect(rect, radius, radius); path = path.united(tmpPath); } drawFocusRingForPath(m_data->p(), path, color, m_data->antiAliasingForRectsAndLines); } void GraphicsContext::drawLineForText(const FloatPoint& origin, float width, bool) { if (paintingDisabled()) return; FloatPoint startPoint = origin; FloatPoint endPoint = origin + FloatSize(width, 0); // If paintengine type is X11 to avoid artifacts // like bug https://bugs.webkit.org/show_bug.cgi?id=42248 #if defined(Q_WS_X11) QPainter* p = m_data->p(); if (p->paintEngine()->type() == QPaintEngine::X11) { // If stroke thickness is odd we need decrease Y coordinate by 1 pixel, // because inside method adjustLineToPixelBoundaries(...), which // called from drawLine(...), Y coordinate will be increased by 0.5f // and then inside Qt painting engine will be rounded to next greater // integer value. float strokeWidth = strokeThickness(); if (static_cast(strokeWidth) % 2) { startPoint.setY(startPoint.y() - 1); endPoint.setY(endPoint.y() - 1); } } #endif // defined(Q_WS_X11) // FIXME: Loss of precision here. Might consider rounding. drawLine(IntPoint(startPoint.x(), startPoint.y()), IntPoint(endPoint.x(), endPoint.y())); } /* * NOTE: This code is completely based upon the one from * Source/WebCore/platform/graphics/cairo/DrawErrorUnderline.{h|cpp} * * Draws an error underline that looks like one of: * * H E H * /\ /\ /\ /\ /\ - * A/ \ / \ / \ A/ \ / \ | * \ \ / \ / /D \ \ / \ | * \ \/ C \/ / \ \/ C \ | height = heightSquares * square * \ /\ F / \ F /\ \ | * \ / \ / \ / \ \G | * \ / \ / \ / \ / | * \/ \/ \/ \/ - * B B * |---| * unitWidth = (heightSquares - 1) * square * * The x, y, width, height passed in give the desired bounding box; * x/width are adjusted to make the underline a integer number of units wide. */ static void drawErrorUnderline(QPainter *painter, qreal x, qreal y, qreal width, qreal height) { const qreal heightSquares = 2.5; qreal square = height / heightSquares; qreal halfSquare = 0.5 * square; qreal unitWidth = (heightSquares - 1.0) * square; int widthUnits = static_cast((width + 0.5 * unitWidth) / unitWidth); x += 0.5 * (width - widthUnits * unitWidth); width = widthUnits * unitWidth; qreal bottom = y + height; qreal top = y; QPainterPath path; // Bottom of squiggle. path.moveTo(x - halfSquare, top + halfSquare); // A int i = 0; for (i = 0; i < widthUnits; i += 2) { qreal middle = x + (i + 1) * unitWidth; qreal right = x + (i + 2) * unitWidth; path.lineTo(middle, bottom); // B if (i + 2 == widthUnits) path.lineTo(right + halfSquare, top + halfSquare); // D else if (i + 1 != widthUnits) path.lineTo(right, top + square); // C } // Top of squiggle. for (i -= 2; i >= 0; i -= 2) { qreal left = x + i * unitWidth; qreal middle = x + (i + 1) * unitWidth; qreal right = x + (i + 2) * unitWidth; if (i + 1 == widthUnits) path.lineTo(middle + halfSquare, bottom - halfSquare); // G else { if (i + 2 == widthUnits) path.lineTo(right, top); // E path.lineTo(middle, bottom - halfSquare); // F } path.lineTo(left, top); // H } painter->drawPath(path); } void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& origin, float width, DocumentMarkerLineStyle style) { if (paintingDisabled()) return; QPainter* painter = platformContext(); const QPen originalPen = painter->pen(); switch (style) { case DocumentMarkerSpellingLineStyle: painter->setPen(Qt::red); break; case DocumentMarkerGrammarLineStyle: painter->setPen(Qt::green); break; default: return; } drawErrorUnderline(painter, origin.x(), origin.y(), width, cMisspellingLineThickness); painter->setPen(originalPen); } FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect, RoundingMode) { // It is not enough just to round to pixels in device space. The rotation part of the // affine transform matrix to device space can mess with this conversion if we have a // rotating image like the hands of the world clock widget. We just need the scale, so // we get the affine transform matrix and extract the scale. QPainter* painter = platformContext(); QTransform deviceTransform = painter->deviceTransform(); if (deviceTransform.isIdentity()) return frect; qreal deviceScaleX = sqrtf(deviceTransform.m11() * deviceTransform.m11() + deviceTransform.m12() * deviceTransform.m12()); qreal deviceScaleY = sqrtf(deviceTransform.m21() * deviceTransform.m21() + deviceTransform.m22() * deviceTransform.m22()); QPoint deviceOrigin(frect.x() * deviceScaleX, frect.y() * deviceScaleY); QPoint deviceLowerRight(frect.maxX() * deviceScaleX, frect.maxY() * deviceScaleY); // Don't let the height or width round to 0 unless either was originally 0 if (deviceOrigin.y() == deviceLowerRight.y() && frect.height()) deviceLowerRight.setY(deviceLowerRight.y() + 1); if (deviceOrigin.x() == deviceLowerRight.x() && frect.width()) deviceLowerRight.setX(deviceLowerRight.x() + 1); FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x() / deviceScaleX, deviceOrigin.y() / deviceScaleY); FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x() / deviceScaleX, deviceLowerRight.y() / deviceScaleY); return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin); } void GraphicsContext::setPlatformShadow(const FloatSize& size, float blur, const Color& color, ColorSpace colorSpace) { // Qt doesn't support shadows natively, they are drawn manually in the draw* // functions if (m_state.shadowsIgnoreTransforms) { // Meaning that this graphics context is associated with a CanvasRenderingContext // We flip the height since CG and HTML5 Canvas have opposite Y axis m_state.shadowOffset = FloatSize(size.width(), -size.height()); } m_data->shadow->setShadowValues(FloatSize(m_state.shadowBlur, m_state.shadowBlur), m_state.shadowOffset, color, colorSpace, m_state.shadowsIgnoreTransforms); } void GraphicsContext::clearPlatformShadow() { m_data->shadow->clear(); } void GraphicsContext::pushTransparencyLayerInternal(const QRect &rect, qreal opacity, QPixmap& alphaMask) { QPainter* p = m_data->p(); QTransform deviceTransform = p->transform(); QRect deviceClip = deviceTransform.mapRect(rect); alphaMask = alphaMask.transformed(deviceTransform); if (alphaMask.width() != deviceClip.width() || alphaMask.height() != deviceClip.height()) alphaMask = alphaMask.scaled(deviceClip.width(), deviceClip.height()); m_data->layers.push(new TransparencyLayer(p, deviceClip, 1.0, alphaMask)); } void GraphicsContext::beginPlatformTransparencyLayer(float opacity) { if (paintingDisabled()) return; int x, y, w, h; x = y = 0; QPainter* p = m_data->p(); const QPaintDevice* device = p->device(); w = device->width(); h = device->height(); if (p->hasClipping()) { QRectF clip = m_data->clipBoundingRect(); QRectF deviceClip = p->transform().mapRect(clip); x = int(qBound(qreal(0), deviceClip.x(), (qreal)w)); y = int(qBound(qreal(0), deviceClip.y(), (qreal)h)); w = int(qBound(qreal(0), deviceClip.width(), (qreal)w) + 2); h = int(qBound(qreal(0), deviceClip.height(), (qreal)h) + 2); } QPixmap emptyAlphaMask; m_data->layers.push(new TransparencyLayer(p, QRect(x, y, w, h), opacity, emptyAlphaMask)); ++m_data->layerCount; } void GraphicsContext::endPlatformTransparencyLayer() { if (paintingDisabled()) return; TransparencyLayer* layer = m_data->layers.pop(); if (!layer->alphaMask.isNull()) { layer->painter.resetTransform(); layer->painter.setCompositionMode(QPainter::CompositionMode_DestinationIn); layer->painter.drawPixmap(QPoint(), layer->alphaMask); } else --m_data->layerCount; // see the comment for layerCount layer->painter.end(); QPainter* p = m_data->p(); p->save(); p->resetTransform(); p->setOpacity(layer->opacity); p->drawPixmap(layer->offset, layer->pixmap); p->restore(); delete layer; } bool GraphicsContext::supportsTransparencyLayers() { return true; } void GraphicsContext::clearRect(const FloatRect& rect) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPainter::CompositionMode currentCompositionMode = p->compositionMode(); p->setCompositionMode(QPainter::CompositionMode_Source); p->fillRect(rect, Qt::transparent); p->setCompositionMode(currentCompositionMode); } void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth) { if (paintingDisabled()) return; Path path; path.addRect(rect); float previousStrokeThickness = strokeThickness(); if (lineWidth != previousStrokeThickness) setStrokeThickness(lineWidth); strokePath(path); if (lineWidth != previousStrokeThickness) setStrokeThickness(previousStrokeThickness); } void GraphicsContext::setLineCap(LineCap lc) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPen nPen = p->pen(); nPen.setCapStyle(toQtLineCap(lc)); p->setPen(nPen); } void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) { QPainter* p = m_data->p(); QPen pen = p->pen(); unsigned dashLength = dashes.size(); if (dashLength) { QVector pattern; unsigned count = dashLength; if (dashLength % 2) count *= 2; float penWidth = narrowPrecisionToFloat(double(pen.widthF())); for (unsigned i = 0; i < count; i++) pattern.append(dashes[i % dashLength] / penWidth); pen.setDashPattern(pattern); pen.setDashOffset(dashOffset / penWidth); } else pen.setStyle(Qt::SolidLine); p->setPen(pen); } void GraphicsContext::setLineJoin(LineJoin lj) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPen nPen = p->pen(); nPen.setJoinStyle(toQtLineJoin(lj)); p->setPen(nPen); } void GraphicsContext::setMiterLimit(float limit) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPen nPen = p->pen(); nPen.setMiterLimit(limit); p->setPen(nPen); } void GraphicsContext::setAlpha(float opacity) { if (paintingDisabled()) return; QPainter* p = m_data->p(); p->setOpacity(opacity); } void GraphicsContext::setPlatformCompositeOperation(CompositeOperator op) { if (paintingDisabled()) return; m_data->p()->setCompositionMode(toQtCompositionMode(op)); } void GraphicsContext::clip(const Path& path) { if (paintingDisabled()) return; QPainterPath clipPath = path.platformPath(); clipPath.setFillRule(Qt::WindingFill); m_data->p()->setClipPath(clipPath, Qt::IntersectClip); } void GraphicsContext::canvasClip(const Path& path) { clip(path); } void GraphicsContext::clipOut(const Path& path) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPainterPath clippedOut = path.platformPath(); QPainterPath newClip; newClip.setFillRule(Qt::OddEvenFill); if (p->hasClipping()) { newClip.addRect(m_data->clipBoundingRect()); newClip.addPath(clippedOut); p->setClipPath(newClip, Qt::IntersectClip); } else { QRect windowRect = p->transform().inverted().mapRect(p->window()); newClip.addRect(windowRect); newClip.addPath(clippedOut.intersected(newClip)); p->setClipPath(newClip); } } void GraphicsContext::translate(float x, float y) { if (paintingDisabled()) return; m_data->p()->translate(x, y); } void GraphicsContext::rotate(float radians) { if (paintingDisabled()) return; QTransform rotation = QTransform().rotateRadians(radians); m_data->p()->setTransform(rotation, true); } void GraphicsContext::scale(const FloatSize& s) { if (paintingDisabled()) return; m_data->p()->scale(s.width(), s.height()); } void GraphicsContext::clipOut(const IntRect& rect) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPainterPath newClip; newClip.setFillRule(Qt::OddEvenFill); if (p->hasClipping()) { newClip.addRect(m_data->clipBoundingRect()); newClip.addRect(QRect(rect)); p->setClipPath(newClip, Qt::IntersectClip); } else { QRect clipOutRect(rect); QRect window = p->transform().inverted().mapRect(p->window()); clipOutRect &= window; newClip.addRect(window); newClip.addRect(clipOutRect); p->setClipPath(newClip); } } void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness) { if (paintingDisabled()) return; clip(rect); QPainterPath path; // Add outer ellipse path.addEllipse(QRectF(rect.x(), rect.y(), rect.width(), rect.height())); // Add inner ellipse. path.addEllipse(QRectF(rect.x() + thickness, rect.y() + thickness, rect.width() - (thickness * 2), rect.height() - (thickness * 2))); path.setFillRule(Qt::OddEvenFill); QPainter* p = m_data->p(); const bool antiAlias = p->testRenderHint(QPainter::Antialiasing); p->setRenderHint(QPainter::Antialiasing, true); p->setClipPath(path, Qt::IntersectClip); p->setRenderHint(QPainter::Antialiasing, antiAlias); } void GraphicsContext::concatCTM(const AffineTransform& transform) { if (paintingDisabled()) return; m_data->p()->setWorldTransform(transform, true); } void GraphicsContext::setCTM(const AffineTransform& transform) { if (paintingDisabled()) return; m_data->p()->setWorldTransform(transform); } #if ENABLE(3D_RENDERING) TransformationMatrix GraphicsContext::get3DTransform() const { if (paintingDisabled()) return TransformationMatrix(); return platformContext()->combinedTransform(); } void GraphicsContext::concat3DTransform(const TransformationMatrix& transform) { if (paintingDisabled()) return; m_data->p()->setWorldTransform(transform, true); } void GraphicsContext::set3DTransform(const TransformationMatrix& transform) { if (paintingDisabled()) return; m_data->p()->setWorldTransform(transform, false); } #endif void GraphicsContext::setURLForRect(const KURL&, const IntRect&) { notImplemented(); } void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace) { if (paintingDisabled() || !color.isValid()) return; QPainter* p = m_data->p(); QPen newPen(p->pen()); m_data->solidColor.setColor(color); newPen.setBrush(m_data->solidColor); p->setPen(newPen); } void GraphicsContext::setPlatformStrokeStyle(StrokeStyle strokeStyle) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPen newPen(p->pen()); newPen.setStyle(toQPenStyle(strokeStyle)); p->setPen(newPen); } void GraphicsContext::setPlatformStrokeThickness(float thickness) { if (paintingDisabled()) return; QPainter* p = m_data->p(); QPen newPen(p->pen()); newPen.setWidthF(thickness); p->setPen(newPen); } void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace) { if (paintingDisabled() || !color.isValid()) return; m_data->solidColor.setColor(color); m_data->p()->setBrush(m_data->solidColor); } void GraphicsContext::setPlatformShouldAntialias(bool enable) { if (paintingDisabled()) return; m_data->p()->setRenderHint(QPainter::Antialiasing, enable); } #if OS(WINDOWS) HDC GraphicsContext::getWindowsContext(const IntRect& dstRect, bool supportAlphaBlend, bool mayCreateBitmap) { // painting through native HDC is only supported for plugin, where mayCreateBitmap is always true Q_ASSERT(mayCreateBitmap); if (dstRect.isEmpty()) return 0; // Create a bitmap DC in which to draw. BITMAPINFO bitmapInfo; bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bitmapInfo.bmiHeader.biWidth = dstRect.width(); bitmapInfo.bmiHeader.biHeight = dstRect.height(); bitmapInfo.bmiHeader.biPlanes = 1; bitmapInfo.bmiHeader.biBitCount = 32; bitmapInfo.bmiHeader.biCompression = BI_RGB; bitmapInfo.bmiHeader.biSizeImage = 0; bitmapInfo.bmiHeader.biXPelsPerMeter = 0; bitmapInfo.bmiHeader.biYPelsPerMeter = 0; bitmapInfo.bmiHeader.biClrUsed = 0; bitmapInfo.bmiHeader.biClrImportant = 0; void* pixels = 0; HBITMAP bitmap = ::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0); if (!bitmap) return 0; HDC displayDC = ::GetDC(0); HDC bitmapDC = ::CreateCompatibleDC(displayDC); ::ReleaseDC(0, displayDC); ::SelectObject(bitmapDC, bitmap); // Fill our buffer with clear if we're going to alpha blend. if (supportAlphaBlend) { BITMAP bmpInfo; GetObject(bitmap, sizeof(bmpInfo), &bmpInfo); int bufferSize = bmpInfo.bmWidthBytes * bmpInfo.bmHeight; memset(bmpInfo.bmBits, 0, bufferSize); } #if !OS(WINCE) // Make sure we can do world transforms. SetGraphicsMode(bitmapDC, GM_ADVANCED); // Apply a translation to our context so that the drawing done will be at (0,0) of the bitmap. XFORM xform; xform.eM11 = 1.0f; xform.eM12 = 0.0f; xform.eM21 = 0.0f; xform.eM22 = 1.0f; xform.eDx = -dstRect.x(); xform.eDy = -dstRect.y(); ::SetWorldTransform(bitmapDC, &xform); #endif return bitmapDC; } void GraphicsContext::releaseWindowsContext(HDC hdc, const IntRect& dstRect, bool supportAlphaBlend, bool mayCreateBitmap) { // painting through native HDC is only supported for plugin, where mayCreateBitmap is always true Q_ASSERT(mayCreateBitmap); if (hdc) { if (!dstRect.isEmpty()) { HBITMAP bitmap = static_cast(GetCurrentObject(hdc, OBJ_BITMAP)); BITMAP info; GetObject(bitmap, sizeof(info), &info); ASSERT(info.bmBitsPixel == 32); QPixmap pixmap = qt_pixmapFromWinHBITMAP(bitmap, supportAlphaBlend ? HBitmapPremultipliedAlpha : HBitmapNoAlpha); m_data->p()->drawPixmap(dstRect, pixmap); ::DeleteObject(bitmap); } ::DeleteDC(hdc); } } #endif void GraphicsContext::setImageInterpolationQuality(InterpolationQuality quality) { m_data->imageInterpolationQuality = quality; switch (quality) { case InterpolationNone: case InterpolationLow: // use nearest-neigbor m_data->p()->setRenderHint(QPainter::SmoothPixmapTransform, false); break; case InterpolationMedium: case InterpolationHigh: // use the filter m_data->p()->setRenderHint(QPainter::SmoothPixmapTransform, true); break; case InterpolationDefault: default: m_data->p()->setRenderHint(QPainter::SmoothPixmapTransform, m_data->initialSmoothPixmapTransformHint); break; }; } InterpolationQuality GraphicsContext::imageInterpolationQuality() const { return m_data->imageInterpolationQuality; } void GraphicsContext::takeOwnershipOfPlatformContext() { m_data->takeOwnershipOfPlatformContext(); } } // vim: ts=4 sw=4 et