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>
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
20 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
28 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35 #include "AffineTransform.h"
38 #include "GraphicsContext.h"
39 #include "ImageBuffer.h"
42 #include "NotImplemented.h"
47 #include <QPainterPath>
48 #include <QPaintDevice>
53 #define M_PI 3.14159265358979323846
58 static inline QPainter::CompositionMode toQtCompositionMode(CompositeOperator op)
62 return QPainter::CompositionMode_Clear;
64 return QPainter::CompositionMode_Source;
65 case CompositeSourceOver:
66 return QPainter::CompositionMode_SourceOver;
67 case CompositeSourceIn:
68 return QPainter::CompositionMode_SourceIn;
69 case CompositeSourceOut:
70 return QPainter::CompositionMode_SourceOut;
71 case CompositeSourceAtop:
72 return QPainter::CompositionMode_SourceAtop;
73 case CompositeDestinationOver:
74 return QPainter::CompositionMode_DestinationOver;
75 case CompositeDestinationIn:
76 return QPainter::CompositionMode_DestinationIn;
77 case CompositeDestinationOut:
78 return QPainter::CompositionMode_DestinationOut;
79 case CompositeDestinationAtop:
80 return QPainter::CompositionMode_DestinationAtop;
82 return QPainter::CompositionMode_Xor;
83 case CompositePlusDarker:
84 return QPainter::CompositionMode_SourceOver;
85 case CompositeHighlight:
86 return QPainter::CompositionMode_SourceOver;
87 case CompositePlusLighter:
88 return QPainter::CompositionMode_SourceOver;
91 return QPainter::CompositionMode_SourceOver;
94 static inline Qt::PenCapStyle toQtLineCap(LineCap lc)
102 return Qt::SquareCap;
108 static inline Qt::PenJoinStyle toQtLineJoin(LineJoin lj)
112 return Qt::SvgMiterJoin;
114 return Qt::RoundJoin;
116 return Qt::BevelJoin;
119 return Qt::MiterJoin;
122 static Qt::PenStyle toQPenStyle(StrokeStyle style)
129 return Qt::SolidLine;
138 qWarning("couldn't recognize the pen style");
142 struct TransparencyLayer
144 TransparencyLayer(const QPainter* p, const QRect &rect)
145 : pixmap(rect.width(), rect.height())
147 offset = rect.topLeft();
148 pixmap.fill(Qt::transparent);
149 painter.begin(&pixmap);
150 painter.translate(-offset);
151 painter.setPen(p->pen());
152 painter.setBrush(p->brush());
153 painter.setTransform(p->transform(), true);
154 painter.setOpacity(p->opacity());
155 painter.setFont(p->font());
156 painter.setCompositionMode(p->compositionMode());
157 painter.setClipPath(p->clipPath());
169 TransparencyLayer(const TransparencyLayer &) {}
170 TransparencyLayer & operator=(const TransparencyLayer &) { return *this; }
182 bool isNull() { return !x && !y && !blur; }
191 class GraphicsContextPlatformPrivate
194 GraphicsContextPlatformPrivate(QPainter* painter);
195 ~GraphicsContextPlatformPrivate();
199 if (layers.isEmpty()) {
205 return &layers.top()->painter;
208 QPaintDevice* device;
210 QStack<TransparencyLayer *> layers;
215 // Only used by SVG for now.
216 QPainterPath currentPath;
223 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate(QPainter* p)
226 device = painter ? painter->device() : 0;
229 // FIXME: Maybe only enable in SVG mode?
231 painter->setRenderHint(QPainter::Antialiasing);
234 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
238 GraphicsContext::GraphicsContext(PlatformGraphicsContext* context)
239 : m_common(createGraphicsContextPrivate())
240 , m_data(new GraphicsContextPlatformPrivate(context))
242 setPaintingDisabled(!context);
244 // Make sure the context starts in sync with our state.
245 setPlatformFillColor(fillColor());
246 setPlatformStrokeColor(strokeColor());
250 GraphicsContext::~GraphicsContext()
252 while(!m_data->layers.isEmpty())
253 endTransparencyLayer();
255 destroyGraphicsContextPrivate(m_common);
259 PlatformGraphicsContext* GraphicsContext::platformContext() const
264 AffineTransform GraphicsContext::getCTM() const
266 return platformContext()->combinedMatrix();
269 void GraphicsContext::savePlatformState()
274 void GraphicsContext::restorePlatformState()
276 m_data->p()->restore();
279 /* FIXME: DISABLED WHILE MERGING BACK FROM UNITY
280 void GraphicsContext::drawTextShadow(const TextRun& run, const IntPoint& point, const FontStyle& style)
282 if (paintingDisabled())
285 if (m_data->shadow.isNull())
288 TextShadow* shadow = &m_data->shadow;
290 if (shadow->blur <= 0) {
292 setPen(shadow->color);
293 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
296 const int thickness = shadow->blur;
297 // FIXME: OPTIMIZE: limit the area to only the actually painted area + 2*thickness
298 const int w = m_data->p()->device()->width();
299 const int h = m_data->p()->device()->height();
300 const QRgb color = qRgb(255, 255, 255);
301 const QRgb bgColor = qRgb(0, 0, 0);
302 QImage image(QSize(w, h), QImage::Format_ARGB32);
309 m_data->redirect = &p;
310 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
311 m_data->redirect = 0;
315 int md = thickness * thickness; // max-dist^2
317 // blur map/precalculated shadow-decay
318 float* bmap = (float*) alloca(sizeof(float) * (md + 1));
319 for (int n = 0; n <= md; n++) {
321 f = n / (float) (md + 1);
326 float factor = 0.0; // maximal potential opacity-sum
327 for (int n = -thickness; n <= thickness; n++) {
328 for (int m = -thickness; m <= thickness; m++) {
329 int d = n * n + m * m;
336 float* amap = (float*) alloca(sizeof(float) * (h * w));
337 memset(amap, 0, h * w * (sizeof(float)));
339 for (int j = thickness; j<h-thickness; j++) {
340 for (int i = thickness; i<w-thickness; i++) {
341 QRgb col = image.pixel(i,j);
345 float g = qAlpha(col);
348 for (int n = -thickness; n <= thickness; n++) {
349 for (int m = -thickness; m <= thickness; m++) {
350 int d = n * n + m * m;
355 amap[(i + m) + (j + n) * w] += (g * f);
361 QImage res(QSize(w,h),QImage::Format_ARGB32);
362 int r = shadow->color.red();
363 int g = shadow->color.green();
364 int b = shadow->color.blue();
365 int a1 = shadow->color.alpha();
367 // arbitratry factor adjustment to make shadows more solid.
368 factor = 1.333 / factor;
370 for (int j = 0; j < h; j++) {
371 for (int i = 0; i < w; i++) {
372 int a = (int) (amap[i + j * w] * factor * a1);
376 res.setPixel(i,j, qRgba(r, g, b, a));
380 m_data->p()->drawImage(0, 0, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither);
385 // Draws a filled rectangle with a stroked border.
386 void GraphicsContext::drawRect(const IntRect& rect)
388 if (paintingDisabled())
391 m_data->p()->drawRect(rect);
394 // FIXME: Now that this is refactored, it should be shared by all contexts.
395 static void adjustLineToPixelBoundaries(FloatPoint& p1, FloatPoint& p2, float strokeWidth,
396 const StrokeStyle& penStyle)
398 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
399 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
400 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
401 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
402 if (penStyle == DottedStroke || penStyle == DashedStroke) {
403 if (p1.x() == p2.x()) {
404 p1.setY(p1.y() + strokeWidth);
405 p2.setY(p2.y() - strokeWidth);
407 p1.setX(p1.x() + strokeWidth);
408 p2.setX(p2.x() - strokeWidth);
412 if (((int) strokeWidth) % 2) {
413 if (p1.x() == p2.x()) {
414 // We're a vertical line. Adjust our x.
415 p1.setX(p1.x() + 0.5);
416 p2.setX(p2.x() + 0.5);
418 // We're a horizontal line. Adjust our y.
419 p1.setY(p1.y() + 0.5);
420 p2.setY(p2.y() + 0.5);
425 // This is only used to draw borders.
426 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
428 if (paintingDisabled())
431 FloatPoint p1 = point1;
432 FloatPoint p2 = point2;
434 adjustLineToPixelBoundaries(p1, p2, strokeThickness(), strokeStyle());
435 m_data->p()->drawLine(p1, p2);
438 // This method is only used to draw the little circles used in lists.
439 void GraphicsContext::drawEllipse(const IntRect& rect)
441 if (paintingDisabled())
444 m_data->p()->drawEllipse(rect);
447 void GraphicsContext::strokeArc(const IntRect& rect, int startAngle, int angleSpan)
449 if (paintingDisabled() || strokeStyle() == NoStroke || strokeThickness() <= 0.0f || !strokeColor().alpha())
452 m_data->p()->drawArc(rect, startAngle * 16, angleSpan * 16);
455 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
457 if (paintingDisabled())
463 QPolygonF polygon(npoints);
465 for (size_t i = 0; i < npoints; i++)
466 polygon[i] = points[i];
468 QPainter *p = m_data->p();
470 p->setRenderHint(QPainter::Antialiasing, shouldAntialias);
471 p->drawConvexPolygon(polygon);
475 void GraphicsContext::fillRect(const IntRect& rect, const Color& c)
477 if (paintingDisabled())
480 m_data->p()->fillRect(rect, QColor(c));
483 void GraphicsContext::fillRect(const FloatRect& rect, const Color& c)
485 if (paintingDisabled())
488 m_data->p()->fillRect(rect, QColor(c));
491 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color)
493 if (paintingDisabled() || !color.alpha())
496 Path path = Path::createRoundedRectangle(rect, topLeft, topRight, bottomLeft, bottomRight);
497 m_data->p()->fillPath(*path.platformPath(), QColor(color));
500 void GraphicsContext::beginPath()
502 m_data->currentPath = QPainterPath();
505 void GraphicsContext::addPath(const Path& path)
507 m_data->currentPath = *(path.platformPath());
510 void GraphicsContext::setFillRule(WindRule rule)
512 m_data->currentPath.setFillRule(rule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill);
515 PlatformPath* GraphicsContext::currentPath()
517 return &m_data->currentPath;
520 void GraphicsContext::clip(const IntRect& rect)
522 if (paintingDisabled())
525 QPainter *p = m_data->p();
526 if (p->clipRegion().isEmpty())
527 p->setClipRect(rect);
528 else p->setClipRect(rect, Qt::IntersectClip);
532 * Focus ring handling is not handled here. Qt style in
533 * RenderTheme handles drawing focus on widgets which
536 void setFocusRingColorChangeFunction(void (*)()) { }
537 Color focusRingColor() { return Color(0, 0, 0); }
538 void GraphicsContext::drawFocusRing(const Color& color)
540 if (paintingDisabled())
543 const Vector<IntRect>& rects = focusRingRects();
544 unsigned rectCount = rects.size();
546 if (rects.size() == 0)
549 QPainter *p = m_data->p();
551 const QPen oldPen = p->pen();
552 const QBrush oldBrush = p->brush();
554 QPen nPen = p->pen();
555 nPen.setColor(color);
556 p->setBrush(Qt::NoBrush);
557 nPen.setStyle(Qt::DotLine);
560 // FIXME How do we do a bounding outline with Qt?
562 for (int i = 0; i < rectCount; ++i)
563 path.addRect(QRectF(rects[i]));
564 QPainterPathStroker stroker;
565 QPainterPath newPath = stroker.createStroke(path);
566 p->strokePath(newPath, nPen);
568 for (int i = 0; i < rectCount; ++i)
569 p->drawRect(QRectF(rects[i]));
572 p->setBrush(oldBrush);
575 void GraphicsContext::drawLineForText(const IntPoint& origin, int width, bool printing)
577 if (paintingDisabled())
580 IntPoint endPoint = origin + IntSize(width, 0);
581 drawLine(origin, endPoint);
584 void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint&,
585 int width, bool grammar)
587 if (paintingDisabled())
593 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
596 rect = m_data->p()->deviceMatrix().mapRect(rect);
598 QRect result = rect.toRect(); //round it
599 return FloatRect(QRectF(result));
602 void GraphicsContext::setShadow(const IntSize& pos, int blur, const Color &color)
604 if (paintingDisabled())
607 m_data->shadow.x = pos.width();
608 m_data->shadow.y = pos.height();
609 m_data->shadow.blur = blur;
610 m_data->shadow.color = color;
613 void GraphicsContext::clearShadow()
615 if (paintingDisabled())
618 m_data->shadow = TextShadow();
621 void GraphicsContext::beginTransparencyLayer(float opacity)
623 if (paintingDisabled())
628 w = m_data->device->width();
629 h = m_data->device->height();
631 QPainter *p = m_data->p();
632 QRectF clip = p->clipPath().boundingRect();
634 QTransform transform = p->transform().inverted(&ok);
636 QRectF deviceClip = transform.mapRect(clip);
637 x = int(qBound(qreal(0), deviceClip.x(), (qreal)w));
638 y = int(qBound(qreal(0), deviceClip.y(), (qreal)h));
639 w = int(qBound(qreal(0), deviceClip.width(), (qreal)w) + 2);
640 h = int(qBound(qreal(0), deviceClip.height(), (qreal)h) + 2);
642 TransparencyLayer * layer = new TransparencyLayer(m_data->p(), QRect(x, y, w, h));
644 layer->opacity = opacity;
645 m_data->layers.push(layer);
648 void GraphicsContext::endTransparencyLayer()
650 if (paintingDisabled())
653 TransparencyLayer *layer = m_data->layers.pop();
654 layer->painter.end();
656 QPainter *p = m_data->p();
659 p->setOpacity(layer->opacity);
660 p->drawPixmap(layer->offset, layer->pixmap);
666 void GraphicsContext::clearRect(const FloatRect& rect)
668 if (paintingDisabled())
671 QPainter *p = m_data->p();
672 QPainter::CompositionMode currentCompositionMode = p->compositionMode();
673 p->setCompositionMode(QPainter::CompositionMode_Source);
675 p->setCompositionMode(currentCompositionMode);
678 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
680 if (paintingDisabled())
683 QPainter *p = m_data->p();
686 QPen nPen = p->pen();
687 nPen.setWidthF(width);
688 p->strokePath(path, nPen);
691 void GraphicsContext::setLineCap(LineCap lc)
693 if (paintingDisabled())
696 QPainter *p = m_data->p();
697 QPen nPen = p->pen();
698 nPen.setCapStyle(toQtLineCap(lc));
702 void GraphicsContext::setLineJoin(LineJoin lj)
704 if (paintingDisabled())
707 QPainter *p = m_data->p();
708 QPen nPen = p->pen();
709 nPen.setJoinStyle(toQtLineJoin(lj));
713 void GraphicsContext::setMiterLimit(float limit)
715 if (paintingDisabled())
718 QPainter *p = m_data->p();
719 QPen nPen = p->pen();
720 nPen.setMiterLimit(limit);
724 void GraphicsContext::setAlpha(float opacity)
726 if (paintingDisabled())
728 QPainter *p = m_data->p();
729 p->setOpacity(opacity);
732 void GraphicsContext::setCompositeOperation(CompositeOperator op)
734 if (paintingDisabled())
737 m_data->p()->setCompositionMode(toQtCompositionMode(op));
740 void GraphicsContext::clip(const Path& path)
742 if (paintingDisabled())
745 m_data->p()->setClipPath(*path.platformPath(), Qt::IntersectClip);
748 void GraphicsContext::clipOut(const Path& path)
750 if (paintingDisabled())
753 QPainter *p = m_data->p();
754 QRectF clipBounds = p->clipPath().boundingRect();
755 QPainterPath clippedOut = *path.platformPath();
756 QPainterPath newClip;
757 newClip.setFillRule(Qt::OddEvenFill);
758 newClip.addRect(clipBounds);
759 newClip.addPath(clippedOut);
761 p->setClipPath(newClip, Qt::IntersectClip);
764 void GraphicsContext::translate(float x, float y)
766 if (paintingDisabled())
769 m_data->p()->translate(x, y);
772 IntPoint GraphicsContext::origin()
774 if (paintingDisabled())
776 const QTransform &transform = m_data->p()->transform();
777 return IntPoint(qRound(transform.dx()), qRound(transform.dy()));
780 void GraphicsContext::rotate(float radians)
782 if (paintingDisabled())
785 m_data->p()->rotate(radians);
788 void GraphicsContext::scale(const FloatSize& s)
790 if (paintingDisabled())
793 m_data->p()->scale(s.width(), s.height());
796 void GraphicsContext::clipOut(const IntRect& rect)
798 if (paintingDisabled())
801 QPainter *p = m_data->p();
802 QRectF clipBounds = p->clipPath().boundingRect();
803 QPainterPath newClip;
804 newClip.setFillRule(Qt::OddEvenFill);
805 newClip.addRect(clipBounds);
806 newClip.addRect(QRect(rect));
808 p->setClipPath(newClip, Qt::IntersectClip);
811 void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
813 if (paintingDisabled())
816 QPainter *p = m_data->p();
817 QRectF clipBounds = p->clipPath().boundingRect();
818 QPainterPath newClip;
819 newClip.setFillRule(Qt::OddEvenFill);
820 newClip.addRect(clipBounds);
821 newClip.addEllipse(QRect(rect));
823 p->setClipPath(newClip, Qt::IntersectClip);
826 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect,
829 if (paintingDisabled())
836 path.addEllipse(QRectF(rect.x(), rect.y(), rect.width(), rect.height()));
838 // Add inner ellipse.
839 path.addEllipse(QRectF(rect.x() + thickness, rect.y() + thickness,
840 rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
842 path.setFillRule(Qt::OddEvenFill);
843 m_data->p()->setClipPath(path, Qt::IntersectClip);
846 void GraphicsContext::concatCTM(const AffineTransform& transform)
848 if (paintingDisabled())
851 m_data->p()->setMatrix(transform, true);
854 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
859 void GraphicsContext::setPlatformFont(const Font& aFont)
861 if (paintingDisabled())
863 m_data->p()->setFont(aFont.font());
866 void GraphicsContext::setPlatformStrokeColor(const Color& color)
868 if (paintingDisabled())
870 QPainter *p = m_data->p();
871 QPen newPen(p->pen());
872 newPen.setColor(color);
876 void GraphicsContext::setPlatformStrokeStyle(const StrokeStyle& strokeStyle)
878 if (paintingDisabled())
880 QPainter *p = m_data->p();
881 QPen newPen(p->pen());
882 newPen.setStyle(toQPenStyle(strokeStyle));
886 void GraphicsContext::setPlatformStrokeThickness(float thickness)
888 if (paintingDisabled())
890 QPainter *p = m_data->p();
891 QPen newPen(p->pen());
892 newPen.setWidthF(thickness);
896 void GraphicsContext::setPlatformFillColor(const Color& color)
898 if (paintingDisabled())
900 m_data->p()->setBrush(QBrush(color));
903 void GraphicsContext::setUseAntialiasing(bool enable)
905 if (paintingDisabled())
907 m_data->p()->setRenderHint(QPainter::Antialiasing, enable);
910 void GraphicsContext::paintBuffer(ImageBuffer* buffer, const IntRect& r)
912 if (paintingDisabled())
914 QPixmap pixmap = *buffer->pixmap();
917 QPainter* painter = platformContext();
918 QPen currentPen = painter->pen();
919 qreal currentOpacity = painter->opacity();
920 QBrush currentBrush = painter->brush();
921 QBrush currentBackground = painter->background();
922 if (painter->isActive())
924 static_cast<QPainter*>(painter)->drawPixmap(r, pixmap);
925 painter->begin(&pixmap);
926 painter->setPen(currentPen);
927 painter->setBrush(currentBrush);
928 painter->setOpacity(currentOpacity);
929 painter->setBackground(currentBackground);
932 void GraphicsContext::drawImage(ImageBuffer* buffer, const FloatRect& srcRect, const FloatRect& dstRect)
934 QPainter* painter = static_cast<QPainter*>(platformContext());
935 QPixmap px = *buffer->pixmap();
938 painter->drawPixmap(dstRect, px, srcRect);