2 * Copyright (C) 2006 Dirk Mueller <mueller@kde.org>
3 * Copyright (C) 2006 Zack Rusin <zack@kde.org>
4 * Copyright (C) 2006 George Staikos <staikos@kde.org>
5 * Copyright (C) 2006 Simon Hausmann <hausmann@kde.org>
6 * Copyright (C) 2006 Allan Sandfeld Jensen <sandfeld@kde.org>
7 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
8 * Copyright (C) 2003, 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
9 * Copyright (C) 2008 Nokia Corporation and/or its subsidiary(-ies).
10 * Copyright (C) 2008 Dirk Schulze <vbs85@gmx.de>
11 * Copyright (C) 2010 Sencha, Inc.
13 * All rights reserved.
15 * Redistribution and use in source and binary forms, with or without
16 * modification, are permitted provided that the following conditions
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 * 2. Redistributions in binary form must reproduce the above copyright
21 * notice, this list of conditions and the following disclaimer in the
22 * documentation and/or other materials provided with the distribution.
24 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
25 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
32 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 #include "GraphicsContext.h"
44 #include "AffineTransform.h"
46 #include "ContextShadow.h"
47 #include "FloatConversion.h"
49 #include "GraphicsContextPrivate.h"
50 #include "ImageBuffer.h"
51 #include "NotImplemented.h"
55 #include "TransparencyLayer.h"
60 #include <QPaintDevice>
61 #include <QPaintEngine>
63 #include <QPainterPath>
70 #define M_PI 3.14159265358979323846
75 QPainter::CompositionMode GraphicsContext::toQtCompositionMode(CompositeOperator op)
79 return QPainter::CompositionMode_Clear;
81 return QPainter::CompositionMode_Source;
82 case CompositeSourceOver:
83 return QPainter::CompositionMode_SourceOver;
84 case CompositeSourceIn:
85 return QPainter::CompositionMode_SourceIn;
86 case CompositeSourceOut:
87 return QPainter::CompositionMode_SourceOut;
88 case CompositeSourceAtop:
89 return QPainter::CompositionMode_SourceAtop;
90 case CompositeDestinationOver:
91 return QPainter::CompositionMode_DestinationOver;
92 case CompositeDestinationIn:
93 return QPainter::CompositionMode_DestinationIn;
94 case CompositeDestinationOut:
95 return QPainter::CompositionMode_DestinationOut;
96 case CompositeDestinationAtop:
97 return QPainter::CompositionMode_DestinationAtop;
99 return QPainter::CompositionMode_Xor;
100 case CompositePlusDarker:
101 // there is no exact match, but this is the closest
102 return QPainter::CompositionMode_Darken;
103 case CompositeHighlight:
104 return QPainter::CompositionMode_SourceOver;
105 case CompositePlusLighter:
106 return QPainter::CompositionMode_Plus;
109 return QPainter::CompositionMode_SourceOver;
112 static inline Qt::PenCapStyle toQtLineCap(LineCap lc)
120 return Qt::SquareCap;
126 static inline Qt::PenJoinStyle toQtLineJoin(LineJoin lj)
130 return Qt::SvgMiterJoin;
132 return Qt::RoundJoin;
134 return Qt::BevelJoin;
137 return Qt::MiterJoin;
140 static Qt::PenStyle toQPenStyle(StrokeStyle style)
147 return Qt::SolidLine;
156 qWarning("couldn't recognize the pen style");
160 static inline Qt::FillRule toQtFillRule(WindRule rule)
164 return Qt::OddEvenFill;
166 return Qt::WindingFill;
168 qDebug("Qt: unrecognized wind rule!");
169 return Qt::OddEvenFill;
172 class GraphicsContextPlatformPrivate : public Noncopyable {
174 GraphicsContextPlatformPrivate(QPainter* painter);
175 ~GraphicsContextPlatformPrivate();
179 if (layers.isEmpty()) {
185 return &layers.top()->painter;
188 bool antiAliasingForRectsAndLines;
190 QStack<TransparencyLayer*> layers;
191 // Counting real layers. Required by inTransparencyLayer() calls
192 // For example, layers with valid alphaMask are not real layers
196 // reuse this brush for solid color (to prevent expensive QBrush construction)
199 InterpolationQuality imageInterpolationQuality;
201 // Only used by SVG for now.
202 QPainterPath currentPath;
204 ContextShadow shadow;
205 QStack<ContextShadow> shadowStack;
207 bool hasShadow() const
209 return shadow.type != ContextShadow::NoShadow;
217 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate(QPainter* p)
223 solidColor = QBrush(Qt::black);
225 imageInterpolationQuality = InterpolationDefault;
228 // use the default the QPainter was constructed with
229 antiAliasingForRectsAndLines = painter->testRenderHint(QPainter::Antialiasing);
230 // FIXME: Maybe only enable in SVG mode?
231 painter->setRenderHint(QPainter::Antialiasing, true);
232 painter->setRenderHint(QPainter::SmoothPixmapTransform, true);
234 antiAliasingForRectsAndLines = false;
237 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
241 GraphicsContext::GraphicsContext(PlatformGraphicsContext* context)
242 : m_common(createGraphicsContextPrivate())
243 , m_data(new GraphicsContextPlatformPrivate(context))
245 setPaintingDisabled(!context);
247 // Make sure the context starts in sync with our state.
248 setPlatformFillColor(fillColor(), DeviceColorSpace);
249 setPlatformStrokeColor(strokeColor(), DeviceColorSpace);
253 GraphicsContext::~GraphicsContext()
255 while (!m_data->layers.isEmpty())
256 endTransparencyLayer();
258 destroyGraphicsContextPrivate(m_common);
262 PlatformGraphicsContext* GraphicsContext::platformContext() const
267 AffineTransform GraphicsContext::getCTM() const
269 QTransform matrix(platformContext()->combinedTransform());
270 return AffineTransform(matrix.m11(), matrix.m12(), matrix.m21(),
271 matrix.m22(), matrix.dx(), matrix.dy());
274 void GraphicsContext::savePlatformState()
276 if (!m_data->layers.isEmpty() && !m_data->layers.top()->alphaMask.isNull())
277 ++m_data->layers.top()->saveCounter;
279 m_data->shadowStack.push(m_data->shadow);
282 void GraphicsContext::restorePlatformState()
284 if (!m_data->layers.isEmpty() && !m_data->layers.top()->alphaMask.isNull())
285 if (!--m_data->layers.top()->saveCounter)
286 endTransparencyLayer();
288 m_data->p()->restore();
290 if (!m_data->currentPath.isEmpty() && m_common->state.pathTransform.isInvertible()) {
291 QTransform matrix = m_common->state.pathTransform;
292 m_data->currentPath = m_data->currentPath * matrix;
295 if (m_data->shadowStack.isEmpty())
296 m_data->shadow = ContextShadow();
298 m_data->shadow = m_data->shadowStack.pop();
301 // Draws a filled rectangle with a stroked border.
302 // This is only used to draw borders (real fill is done via fillRect), and
303 // thus it must not cast any shadow.
304 void GraphicsContext::drawRect(const IntRect& rect)
306 if (paintingDisabled())
309 QPainter* p = m_data->p();
310 const bool antiAlias = p->testRenderHint(QPainter::Antialiasing);
311 p->setRenderHint(QPainter::Antialiasing, m_data->antiAliasingForRectsAndLines);
315 p->setRenderHint(QPainter::Antialiasing, antiAlias);
318 // This is only used to draw borders.
319 // Must not cast any shadow.
320 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
322 if (paintingDisabled())
325 StrokeStyle style = strokeStyle();
326 Color color = strokeColor();
327 if (style == NoStroke)
330 float width = strokeThickness();
332 FloatPoint p1 = point1;
333 FloatPoint p2 = point2;
334 bool isVerticalLine = (p1.x() == p2.x());
336 QPainter* p = m_data->p();
337 const bool antiAlias = p->testRenderHint(QPainter::Antialiasing);
338 p->setRenderHint(QPainter::Antialiasing, m_data->antiAliasingForRectsAndLines);
339 adjustLineToPixelBoundaries(p1, p2, width, style);
347 patWidth = static_cast<int>(width);
350 patWidth = 3 * static_cast<int>(width);
357 // Do a rect fill of our endpoints. This ensures we always have the
358 // appearance of being a border. We then draw the actual dotted/dashed line.
359 if (isVerticalLine) {
360 p->fillRect(FloatRect(p1.x() - width / 2, p1.y() - width, width, width), QColor(color));
361 p->fillRect(FloatRect(p2.x() - width / 2, p2.y(), width, width), QColor(color));
363 p->fillRect(FloatRect(p1.x() - width, p1.y() - width / 2, width, width), QColor(color));
364 p->fillRect(FloatRect(p2.x(), p2.y() - width / 2, width, width), QColor(color));
367 // Example: 80 pixels with a width of 30 pixels.
368 // Remainder is 20. The maximum pixels of line we could paint
369 // will be 50 pixels.
370 int distance = (isVerticalLine ? (point2.y() - point1.y()) : (point2.x() - point1.x())) - 2*(int)width;
371 int remainder = distance % patWidth;
372 int coverage = distance - remainder;
373 int numSegments = coverage / patWidth;
375 float patternOffset = 0.0f;
376 // Special case 1px dotted borders for speed.
378 patternOffset = 1.0f;
380 bool evenNumberOfSegments = !(numSegments % 2);
382 evenNumberOfSegments = !evenNumberOfSegments;
383 if (evenNumberOfSegments) {
385 patternOffset += patWidth - remainder;
386 patternOffset += remainder / 2;
388 patternOffset = patWidth / 2;
391 patternOffset = (patWidth - remainder) / 2;
395 QVector<qreal> dashes;
396 dashes << qreal(patWidth) / width << qreal(patWidth) / width;
399 pen.setWidthF(width);
400 pen.setCapStyle(Qt::FlatCap);
401 pen.setDashPattern(dashes);
402 pen.setDashOffset(patternOffset / width);
411 p->setRenderHint(QPainter::Antialiasing, antiAlias);
414 // This method is only used to draw the little circles used in lists.
415 void GraphicsContext::drawEllipse(const IntRect& rect)
417 if (paintingDisabled())
420 m_data->p()->drawEllipse(rect);
423 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
425 if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f)
428 QPainter* p = m_data->p();
429 const bool antiAlias = p->testRenderHint(QPainter::Antialiasing);
430 p->setRenderHint(QPainter::Antialiasing, true);
435 if (m_data->hasShadow()) {
437 p->translate(m_data->shadow.offset);
439 pen.setColor(m_data->shadow.color);
441 p->drawArc(rect, startAngle, angleSpan);
444 p->drawArc(rect, startAngle, angleSpan);
446 p->setRenderHint(QPainter::Antialiasing, antiAlias);
449 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
451 if (paintingDisabled())
457 QPolygonF polygon(npoints);
459 for (size_t i = 0; i < npoints; i++)
460 polygon[i] = points[i];
462 QPainter* p = m_data->p();
464 p->setRenderHint(QPainter::Antialiasing, shouldAntialias);
465 if (m_data->hasShadow()) {
467 p->translate(m_data->shadow.offset);
468 if (p->brush().style() != Qt::NoBrush)
469 p->setBrush(QBrush(m_data->shadow.color));
471 if (pen.style() != Qt::NoPen) {
472 pen.setColor(m_data->shadow.color);
475 p->drawConvexPolygon(polygon);
478 p->drawConvexPolygon(polygon);
482 void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint* points, bool antialiased)
484 if (paintingDisabled())
490 QPainterPath path(points[0]);
491 for (size_t i = 1; i < numPoints; ++i)
492 path.lineTo(points[i]);
493 path.setFillRule(Qt::WindingFill);
494 m_data->p()->setClipPath(path, Qt::IntersectClip);
497 QPen GraphicsContext::pen()
499 if (paintingDisabled())
502 QPainter* p = m_data->p();
506 void GraphicsContext::fillPath()
508 if (paintingDisabled())
511 QPainter* p = m_data->p();
512 QPainterPath& path = m_data->currentPath; // Avoid detaching the QPainterPath
513 path.setFillRule(toQtFillRule(fillRule()));
515 if (m_data->hasShadow()) {
516 p->translate(m_data->shadow.offset);
517 p->fillPath(path, m_data->shadow.color);
518 p->translate(-m_data->shadow.offset);
520 if (m_common->state.fillPattern) {
521 AffineTransform affine;
522 p->fillPath(path, QBrush(m_common->state.fillPattern->createPlatformPattern(affine)));
523 } else if (m_common->state.fillGradient) {
524 QBrush brush(*m_common->state.fillGradient->platformGradient());
525 brush.setTransform(m_common->state.fillGradient->gradientSpaceTransform());
526 p->fillPath(path, brush);
528 p->fillPath(path, p->brush());
530 m_data->currentPath = QPainterPath();
533 void GraphicsContext::strokePath()
535 if (paintingDisabled())
538 QPainter* p = m_data->p();
540 QPainterPath& path = m_data->currentPath; // Avoid detaching the QPainterPath
541 path.setFillRule(toQtFillRule(fillRule()));
543 if (m_data->hasShadow()) {
544 p->translate(m_data->shadow.offset);
546 shadowPen.setColor(m_data->shadow.color);
547 p->strokePath(path, shadowPen);
548 p->translate(-m_data->shadow.offset);
550 if (m_common->state.strokePattern) {
551 AffineTransform affine;
552 pen.setBrush(QBrush(m_common->state.strokePattern->createPlatformPattern(affine)));
554 p->strokePath(path, pen);
555 } else if (m_common->state.strokeGradient) {
556 QBrush brush(*m_common->state.strokeGradient->platformGradient());
557 brush.setTransform(m_common->state.strokeGradient->gradientSpaceTransform());
560 p->strokePath(path, pen);
562 p->strokePath(path, pen);
563 m_data->currentPath = QPainterPath();
566 static inline void drawRepeatPattern(QPainter* p, QPixmap* image, const FloatRect& rect, const bool repeatX, const bool repeatY)
568 // Patterns must be painted so that the top left of the first image is anchored at
569 // the origin of the coordinate space
571 int w = image->width();
572 int h = image->height();
574 QRect r(static_cast<int>(rect.x()), static_cast<int>(rect.y()), static_cast<int>(rect.width()), static_cast<int>(rect.height()));
576 // startX, startY is the coordinate of the first image we need to put on the left-top of the rect
577 if (repeatX && repeatY) {
579 // startX, startY is at the left top side of the left-top of the rect
580 startX = r.x() >=0 ? r.x() - (r.x() % w) : r.x() - (w - qAbs(r.x()) % w);
581 startY = r.y() >=0 ? r.y() - (r.y() % h) : r.y() - (h - qAbs(r.y()) % h);
583 if (!repeatX && !repeatY) {
585 // only draw the image once at orgin once, check if need to draw
586 QRect imageRect(0, 0, w, h);
587 if (imageRect.intersects(r)) {
592 } else if (repeatX && !repeatY) {
594 // startY is fixed, but startX change based on the left-top of the rect
595 QRect imageRect(r.x(), 0, r.width(), h);
596 if (imageRect.intersects(r)) {
597 startX = r.x() >=0 ? r.x() - (r.x() % w) : r.x() - (w - qAbs(r.x()) % w);
603 // startX is fixed, but startY change based on the left-top of the rect
604 QRect imageRect(0, r.y(), w, r.height());
605 if (imageRect.intersects(r)) {
607 startY = r.y() >=0 ? r.y() - (r.y() % h) : r.y() - (h - qAbs(r.y()) % h);
619 QRect imageRect(x, y, w, h);
620 QRect intersectRect = imageRect.intersected(r);
621 QPoint destStart(intersectRect.x(), intersectRect.y());
622 QRect sourceRect(intersectRect.x() - imageRect.x(), intersectRect.y() - imageRect.y(), intersectRect.width(), intersectRect.height());
624 p->drawPixmap(destStart, *image, sourceRect);
626 } while (repeatX && x < r.x() + r.width());
629 } while (repeatY && y < r.y() + r.height());
633 void GraphicsContext::fillRect(const FloatRect& rect)
635 if (paintingDisabled())
638 QPainter* p = m_data->p();
639 FloatRect normalizedRect = rect.normalized();
641 QRectF shadowDestRect;
642 QImage* shadowImage = 0;
643 QPainter* pShadow = 0;
645 if (m_data->hasShadow()) {
646 shadowImage = new QImage(roundedIntSize(normalizedRect.size()), QImage::Format_ARGB32_Premultiplied);
647 pShadow = new QPainter(shadowImage);
648 shadowDestRect = normalizedRect;
649 shadowDestRect.translate(m_data->shadow.offset);
651 pShadow->setCompositionMode(QPainter::CompositionMode_Source);
652 pShadow->fillRect(shadowImage->rect(), m_data->shadow.color);
653 pShadow->setCompositionMode(QPainter::CompositionMode_DestinationIn);
656 if (m_common->state.fillPattern) {
657 AffineTransform affine;
658 FloatRect rectM(rect);
659 QBrush brush(m_common->state.fillPattern->createPlatformPattern(affine));
660 QPixmap* image = m_common->state.fillPattern->tileImage()->nativeImageForCurrentFrame();
662 if (m_data->hasShadow()) {
663 drawRepeatPattern(pShadow, image, FloatRect(static_cast<QRectF>(shadowImage->rect())), m_common->state.fillPattern->repeatX(), m_common->state.fillPattern->repeatY());
665 p->drawImage(shadowDestRect, *shadowImage, shadowImage->rect());
667 drawRepeatPattern(p, image, normalizedRect, m_common->state.fillPattern->repeatX(), m_common->state.fillPattern->repeatY());
668 } else if (m_common->state.fillGradient) {
669 QBrush brush(*m_common->state.fillGradient->platformGradient());
670 brush.setTransform(m_common->state.fillGradient->gradientSpaceTransform());
672 if (m_data->hasShadow()) {
673 pShadow->fillRect(shadowImage->rect(), brush);
675 p->drawImage(shadowDestRect, *shadowImage, shadowImage->rect());
677 p->fillRect(normalizedRect, brush);
679 if (m_data->hasShadow()) {
680 pShadow->fillRect(shadowImage->rect(), p->brush());
682 p->drawImage(shadowDestRect, *shadowImage, shadowImage->rect());
684 p->fillRect(normalizedRect, p->brush());
692 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
694 if (paintingDisabled() || !color.isValid())
697 m_data->solidColor.setColor(color);
698 QPainter* p = m_data->p();
700 if (m_data->hasShadow())
701 m_data->shadow.drawShadowRect(p, rect);
703 p->fillRect(rect, m_data->solidColor);
706 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace colorSpace)
708 if (paintingDisabled() || !color.isValid())
711 Path path = Path::createRoundedRectangle(rect, topLeft, topRight, bottomLeft, bottomRight);
712 QPainter* p = m_data->p();
713 if (m_data->hasShadow()) {
714 p->translate(m_data->shadow.offset);
715 p->fillPath(path.platformPath(), m_data->shadow.color);
716 p->translate(-m_data->shadow.offset);
718 p->fillPath(path.platformPath(), QColor(color));
721 void GraphicsContext::beginPath()
723 m_data->currentPath = QPainterPath();
726 void GraphicsContext::addPath(const Path& path)
728 if (!m_data->currentPath.elementCount()) {
729 m_data->currentPath = path.platformPath();
732 m_data->currentPath.addPath(path.platformPath());
735 bool GraphicsContext::inTransparencyLayer() const
737 return m_data->layerCount;
740 PlatformPath* GraphicsContext::currentPath()
742 return &m_data->currentPath;
745 void GraphicsContext::clip(const FloatRect& rect)
747 if (paintingDisabled())
750 m_data->p()->setClipRect(rect, Qt::IntersectClip);
753 void GraphicsContext::clipPath(WindRule clipRule)
755 if (paintingDisabled())
758 QPainter* p = m_data->p();
759 QPainterPath newPath = m_data->currentPath;
760 newPath.setFillRule(clipRule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill);
761 p->setClipPath(newPath);
764 void GraphicsContext::drawFocusRing(const Vector<Path>& paths, int width, int offset, const Color& color)
770 * Focus ring handling is not handled here. Qt style in
771 * RenderTheme handles drawing focus on widgets which
774 void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int /* width */, int /* offset */, const Color& color)
776 if (paintingDisabled() || !color.isValid())
779 unsigned rectCount = rects.size();
784 QPainter* p = m_data->p();
785 const bool antiAlias = p->testRenderHint(QPainter::Antialiasing);
786 p->setRenderHint(QPainter::Antialiasing, m_data->antiAliasingForRectsAndLines);
788 const QPen oldPen = p->pen();
789 const QBrush oldBrush = p->brush();
791 QPen nPen = p->pen();
792 nPen.setColor(color);
793 p->setBrush(Qt::NoBrush);
794 nPen.setStyle(Qt::DotLine);
797 // FIXME How do we do a bounding outline with Qt?
799 for (int i = 0; i < rectCount; ++i)
800 path.addRect(QRectF(rects[i]));
801 QPainterPathStroker stroker;
802 QPainterPath newPath = stroker.createStroke(path);
803 p->strokePath(newPath, nPen);
805 for (unsigned i = 0; i < rectCount; ++i)
806 p->drawRect(QRectF(rects[i]));
809 p->setBrush(oldBrush);
811 p->setRenderHint(QPainter::Antialiasing, antiAlias);
814 void GraphicsContext::drawLineForText(const IntPoint& origin, int width, bool)
816 if (paintingDisabled())
819 IntPoint endPoint = origin + IntSize(width, 0);
820 drawLine(origin, endPoint);
823 void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint&, int, bool)
825 if (paintingDisabled())
831 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
833 // It is not enough just to round to pixels in device space. The rotation part of the
834 // affine transform matrix to device space can mess with this conversion if we have a
835 // rotating image like the hands of the world clock widget. We just need the scale, so
836 // we get the affine transform matrix and extract the scale.
837 QPainter* painter = platformContext();
838 QTransform deviceTransform = painter->deviceTransform();
839 if (deviceTransform.isIdentity())
842 qreal deviceScaleX = sqrtf(deviceTransform.m11() * deviceTransform.m11() + deviceTransform.m12() * deviceTransform.m12());
843 qreal deviceScaleY = sqrtf(deviceTransform.m21() * deviceTransform.m21() + deviceTransform.m22() * deviceTransform.m22());
845 QPoint deviceOrigin(frect.x() * deviceScaleX, frect.y() * deviceScaleY);
846 QPoint deviceLowerRight(frect.right() * deviceScaleX, frect.bottom() * deviceScaleY);
848 // Don't let the height or width round to 0 unless either was originally 0
849 if (deviceOrigin.y() == deviceLowerRight.y() && frect.height())
850 deviceLowerRight.setY(deviceLowerRight.y() + 1);
851 if (deviceOrigin.x() == deviceLowerRight.x() && frect.width())
852 deviceLowerRight.setX(deviceLowerRight.x() + 1);
854 FloatPoint roundedOrigin = FloatPoint(deviceOrigin.x() / deviceScaleX, deviceOrigin.y() / deviceScaleY);
855 FloatPoint roundedLowerRight = FloatPoint(deviceLowerRight.x() / deviceScaleX, deviceLowerRight.y() / deviceScaleY);
856 return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
859 void GraphicsContext::setPlatformShadow(const FloatSize& size, float blur, const Color& color, ColorSpace)
861 // Qt doesn't support shadows natively, they are drawn manually in the draw*
864 if (m_common->state.shadowsIgnoreTransforms) {
865 // Meaning that this graphics context is associated with a CanvasRenderingContext
866 // We flip the height since CG and HTML5 Canvas have opposite Y axis
867 m_common->state.shadowSize = FloatSize(size.width(), -size.height());
868 m_data->shadow = ContextShadow(color, blur, size.width(), -size.height());
870 m_data->shadow = ContextShadow(color, blur, size.width(), size.height());
874 void GraphicsContext::clearPlatformShadow()
876 m_data->shadow.clear();
879 void GraphicsContext::pushTransparencyLayerInternal(const QRect &rect, qreal opacity, QPixmap& alphaMask)
881 m_data->layers.push(new TransparencyLayer(m_data->p(), m_data->p()->transform().mapRect(rect), 1.0, alphaMask));
884 void GraphicsContext::beginTransparencyLayer(float opacity)
886 if (paintingDisabled())
891 QPainter* p = m_data->p();
892 const QPaintDevice* device = p->device();
894 h = device->height();
896 QRectF clip = p->clipPath().boundingRect();
897 QRectF deviceClip = p->transform().mapRect(clip);
898 x = int(qBound(qreal(0), deviceClip.x(), (qreal)w));
899 y = int(qBound(qreal(0), deviceClip.y(), (qreal)h));
900 w = int(qBound(qreal(0), deviceClip.width(), (qreal)w) + 2);
901 h = int(qBound(qreal(0), deviceClip.height(), (qreal)h) + 2);
903 QPixmap emptyAlphaMask;
904 m_data->layers.push(new TransparencyLayer(m_data->p(), QRect(x, y, w, h), opacity, emptyAlphaMask));
905 ++m_data->layerCount;
908 void GraphicsContext::endTransparencyLayer()
910 if (paintingDisabled())
913 TransparencyLayer* layer = m_data->layers.pop();
914 if (!layer->alphaMask.isNull()) {
915 layer->painter.resetTransform();
916 layer->painter.setCompositionMode(QPainter::CompositionMode_DestinationIn);
917 layer->painter.drawPixmap(QPoint(), layer->alphaMask);
919 --m_data->layerCount; // see the comment for layerCount
920 layer->painter.end();
922 QPainter* p = m_data->p();
925 p->setOpacity(layer->opacity);
926 p->drawPixmap(layer->offset, layer->pixmap);
932 void GraphicsContext::clearRect(const FloatRect& rect)
934 if (paintingDisabled())
937 QPainter* p = m_data->p();
938 QPainter::CompositionMode currentCompositionMode = p->compositionMode();
939 if (p->paintEngine()->hasFeature(QPaintEngine::PorterDuff))
940 p->setCompositionMode(QPainter::CompositionMode_Source);
941 p->fillRect(rect, Qt::transparent);
942 if (p->paintEngine()->hasFeature(QPaintEngine::PorterDuff))
943 p->setCompositionMode(currentCompositionMode);
946 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
948 if (paintingDisabled())
953 setStrokeThickness(width);
954 m_data->currentPath = path;
959 void GraphicsContext::setLineCap(LineCap lc)
961 if (paintingDisabled())
964 QPainter* p = m_data->p();
965 QPen nPen = p->pen();
966 nPen.setCapStyle(toQtLineCap(lc));
970 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
972 QPainter* p = m_data->p();
974 unsigned dashLength = dashes.size();
976 QVector<qreal> pattern;
977 unsigned count = dashLength;
981 float penWidth = narrowPrecisionToFloat(double(pen.widthF()));
982 for (unsigned i = 0; i < count; i++)
983 pattern.append(dashes[i % dashLength] / penWidth);
985 pen.setDashPattern(pattern);
986 pen.setDashOffset(dashOffset);
988 pen.setStyle(Qt::SolidLine);
992 void GraphicsContext::setLineJoin(LineJoin lj)
994 if (paintingDisabled())
997 QPainter* p = m_data->p();
998 QPen nPen = p->pen();
999 nPen.setJoinStyle(toQtLineJoin(lj));
1003 void GraphicsContext::setMiterLimit(float limit)
1005 if (paintingDisabled())
1008 QPainter* p = m_data->p();
1009 QPen nPen = p->pen();
1010 nPen.setMiterLimit(limit);
1014 void GraphicsContext::setAlpha(float opacity)
1016 if (paintingDisabled())
1018 QPainter* p = m_data->p();
1019 p->setOpacity(opacity);
1022 void GraphicsContext::setCompositeOperation(CompositeOperator op)
1024 if (paintingDisabled())
1027 if (m_data->p()->paintEngine()->hasFeature(QPaintEngine::PorterDuff))
1028 m_data->p()->setCompositionMode(toQtCompositionMode(op));
1031 void GraphicsContext::clip(const Path& path)
1033 if (paintingDisabled())
1036 m_data->p()->setClipPath(path.platformPath(), Qt::IntersectClip);
1039 void GraphicsContext::canvasClip(const Path& path)
1041 if (paintingDisabled())
1044 QPainterPath clipPath = path.platformPath();
1045 clipPath.setFillRule(Qt::WindingFill);
1046 m_data->p()->setClipPath(clipPath, Qt::IntersectClip);
1049 void GraphicsContext::clipOut(const Path& path)
1051 if (paintingDisabled())
1054 QPainter* p = m_data->p();
1055 QPainterPath clippedOut = path.platformPath();
1056 QPainterPath newClip;
1057 newClip.setFillRule(Qt::OddEvenFill);
1058 if (p->hasClipping()) {
1059 newClip.addRect(p->clipRegion().boundingRect());
1060 newClip.addPath(clippedOut);
1061 p->setClipPath(newClip, Qt::IntersectClip);
1063 QRect windowRect = p->transform().inverted().mapRect(p->window());
1064 newClip.addRect(windowRect);
1065 newClip.addPath(clippedOut.intersected(newClip));
1066 p->setClipPath(newClip);
1070 void GraphicsContext::translate(float x, float y)
1072 if (paintingDisabled())
1075 m_data->p()->translate(x, y);
1077 if (!m_data->currentPath.isEmpty()) {
1079 m_data->currentPath = m_data->currentPath * matrix.translate(-x, -y);
1080 m_common->state.pathTransform.translate(x, y);
1084 IntPoint GraphicsContext::origin()
1086 if (paintingDisabled())
1088 const QTransform &transform = m_data->p()->transform();
1089 return IntPoint(qRound(transform.dx()), qRound(transform.dy()));
1092 void GraphicsContext::rotate(float radians)
1094 if (paintingDisabled())
1097 m_data->p()->rotate(180 / M_PI*radians);
1099 if (!m_data->currentPath.isEmpty()) {
1101 m_data->currentPath = m_data->currentPath * matrix.rotate(-180 / M_PI*radians);
1102 m_common->state.pathTransform.rotate(radians);
1106 void GraphicsContext::scale(const FloatSize& s)
1108 if (paintingDisabled())
1111 m_data->p()->scale(s.width(), s.height());
1113 if (!m_data->currentPath.isEmpty()) {
1115 m_data->currentPath = m_data->currentPath * matrix.scale(1 / s.width(), 1 / s.height());
1116 m_common->state.pathTransform.scaleNonUniform(s.width(), s.height());
1120 void GraphicsContext::clipOut(const IntRect& rect)
1122 if (paintingDisabled())
1125 QPainter* p = m_data->p();
1126 QPainterPath newClip;
1127 newClip.setFillRule(Qt::OddEvenFill);
1128 if (p->hasClipping()) {
1129 newClip.addRect(p->clipRegion().boundingRect());
1130 newClip.addRect(QRect(rect));
1131 p->setClipPath(newClip, Qt::IntersectClip);
1133 QRect clipOutRect(rect);
1134 QRect window = p->transform().inverted().mapRect(p->window());
1135 clipOutRect &= window;
1136 newClip.addRect(window);
1137 newClip.addRect(clipOutRect);
1138 p->setClipPath(newClip);
1142 void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
1144 if (paintingDisabled())
1147 QPainter* p = m_data->p();
1148 QPainterPath newClip;
1149 newClip.setFillRule(Qt::OddEvenFill);
1150 if (p->hasClipping()) {
1151 newClip.addRect(p->clipRegion().boundingRect());
1152 newClip.addEllipse(QRect(rect));
1153 p->setClipPath(newClip, Qt::IntersectClip);
1155 QRect clipOutRect(rect);
1156 QRect window(p->window());
1157 clipOutRect &= window;
1158 newClip.addRect(window);
1159 newClip.addEllipse(clipOutRect);
1160 p->setClipPath(newClip);
1164 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect,
1167 if (paintingDisabled())
1173 // Add outer ellipse
1174 path.addEllipse(QRectF(rect.x(), rect.y(), rect.width(), rect.height()));
1176 // Add inner ellipse.
1177 path.addEllipse(QRectF(rect.x() + thickness, rect.y() + thickness,
1178 rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
1180 path.setFillRule(Qt::OddEvenFill);
1182 QPainter* p = m_data->p();
1184 const bool antiAlias = p->testRenderHint(QPainter::Antialiasing);
1185 p->setRenderHint(QPainter::Antialiasing, true);
1186 p->setClipPath(path, Qt::IntersectClip);
1187 p->setRenderHint(QPainter::Antialiasing, antiAlias);
1190 void GraphicsContext::concatCTM(const AffineTransform& transform)
1192 if (paintingDisabled())
1195 m_data->p()->setWorldTransform(transform, true);
1197 // Transformations to the context shouldn't transform the currentPath.
1198 // We have to undo every change made to the context from the currentPath
1199 // to avoid wrong drawings.
1200 if (!m_data->currentPath.isEmpty() && transform.isInvertible()) {
1201 QTransform matrix = transform.inverse();
1202 m_data->currentPath = m_data->currentPath * matrix;
1203 m_common->state.pathTransform.multiply(transform.toTransformationMatrix());
1207 void GraphicsContext::setURLForRect(const KURL&, const IntRect&)
1212 void GraphicsContext::setPlatformStrokeColor(const Color& color, ColorSpace colorSpace)
1214 if (paintingDisabled() || !color.isValid())
1217 QPainter* p = m_data->p();
1218 QPen newPen(p->pen());
1219 m_data->solidColor.setColor(color);
1220 newPen.setBrush(m_data->solidColor);
1224 void GraphicsContext::setPlatformStrokeStyle(const StrokeStyle& strokeStyle)
1226 if (paintingDisabled())
1228 QPainter* p = m_data->p();
1229 QPen newPen(p->pen());
1230 newPen.setStyle(toQPenStyle(strokeStyle));
1234 void GraphicsContext::setPlatformStrokeThickness(float thickness)
1236 if (paintingDisabled())
1238 QPainter* p = m_data->p();
1239 QPen newPen(p->pen());
1240 newPen.setWidthF(thickness);
1244 void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace)
1246 if (paintingDisabled() || !color.isValid())
1249 m_data->solidColor.setColor(color);
1250 m_data->p()->setBrush(m_data->solidColor);
1253 void GraphicsContext::setPlatformShouldAntialias(bool enable)
1255 if (paintingDisabled())
1257 m_data->p()->setRenderHint(QPainter::Antialiasing, enable);
1262 HDC GraphicsContext::getWindowsContext(const IntRect& dstRect, bool supportAlphaBlend, bool mayCreateBitmap)
1264 // painting through native HDC is only supported for plugin, where mayCreateBitmap is always true
1265 Q_ASSERT(mayCreateBitmap);
1267 if (dstRect.isEmpty())
1270 // Create a bitmap DC in which to draw.
1271 BITMAPINFO bitmapInfo;
1272 bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
1273 bitmapInfo.bmiHeader.biWidth = dstRect.width();
1274 bitmapInfo.bmiHeader.biHeight = dstRect.height();
1275 bitmapInfo.bmiHeader.biPlanes = 1;
1276 bitmapInfo.bmiHeader.biBitCount = 32;
1277 bitmapInfo.bmiHeader.biCompression = BI_RGB;
1278 bitmapInfo.bmiHeader.biSizeImage = 0;
1279 bitmapInfo.bmiHeader.biXPelsPerMeter = 0;
1280 bitmapInfo.bmiHeader.biYPelsPerMeter = 0;
1281 bitmapInfo.bmiHeader.biClrUsed = 0;
1282 bitmapInfo.bmiHeader.biClrImportant = 0;
1285 HBITMAP bitmap = ::CreateDIBSection(0, &bitmapInfo, DIB_RGB_COLORS, &pixels, 0, 0);
1289 HDC displayDC = ::GetDC(0);
1290 HDC bitmapDC = ::CreateCompatibleDC(displayDC);
1291 ::ReleaseDC(0, displayDC);
1293 ::SelectObject(bitmapDC, bitmap);
1295 // Fill our buffer with clear if we're going to alpha blend.
1296 if (supportAlphaBlend) {
1298 GetObject(bitmap, sizeof(bmpInfo), &bmpInfo);
1299 int bufferSize = bmpInfo.bmWidthBytes * bmpInfo.bmHeight;
1300 memset(bmpInfo.bmBits, 0, bufferSize);
1304 // Make sure we can do world transforms.
1305 SetGraphicsMode(bitmapDC, GM_ADVANCED);
1307 // Apply a translation to our context so that the drawing done will be at (0,0) of the bitmap.
1313 xform.eDx = -dstRect.x();
1314 xform.eDy = -dstRect.y();
1315 ::SetWorldTransform(bitmapDC, &xform);
1321 void GraphicsContext::releaseWindowsContext(HDC hdc, const IntRect& dstRect, bool supportAlphaBlend, bool mayCreateBitmap)
1323 // painting through native HDC is only supported for plugin, where mayCreateBitmap is always true
1324 Q_ASSERT(mayCreateBitmap);
1328 if (!dstRect.isEmpty()) {
1330 HBITMAP bitmap = static_cast<HBITMAP>(GetCurrentObject(hdc, OBJ_BITMAP));
1332 GetObject(bitmap, sizeof(info), &info);
1333 ASSERT(info.bmBitsPixel == 32);
1335 QPixmap pixmap = QPixmap::fromWinHBITMAP(bitmap, supportAlphaBlend ? QPixmap::PremultipliedAlpha : QPixmap::NoAlpha);
1336 m_data->p()->drawPixmap(dstRect, pixmap);
1338 ::DeleteObject(bitmap);
1346 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality quality)
1348 m_data->imageInterpolationQuality = quality;
1351 case InterpolationNone:
1352 case InterpolationLow:
1353 // use nearest-neigbor
1354 m_data->p()->setRenderHint(QPainter::SmoothPixmapTransform, false);
1357 case InterpolationDefault:
1358 case InterpolationMedium:
1359 case InterpolationHigh:
1362 m_data->p()->setRenderHint(QPainter::SmoothPixmapTransform, true);
1367 InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1369 return m_data->imageInterpolationQuality;
1374 // vim: ts=4 sw=4 et