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"
41 #include "SVGResourceImage.h"
46 #include <QPainterPath>
47 #include <QPaintDevice>
50 #define M_PI 3.14159265358979323846
53 #define notImplemented() do { fprintf(stderr, "FIXME: UNIMPLEMENTED: %s:%d\n", __FILE__, __LINE__); } while(0)
57 static inline QPainter::CompositionMode toQtCompositionMode(CompositeOperator op)
61 return QPainter::CompositionMode_Clear;
63 return QPainter::CompositionMode_Source;
64 case CompositeSourceOver:
65 return QPainter::CompositionMode_SourceOver;
66 case CompositeSourceIn:
67 return QPainter::CompositionMode_SourceIn;
68 case CompositeSourceOut:
69 return QPainter::CompositionMode_SourceOut;
70 case CompositeSourceAtop:
71 return QPainter::CompositionMode_SourceAtop;
72 case CompositeDestinationOver:
73 return QPainter::CompositionMode_DestinationOver;
74 case CompositeDestinationIn:
75 return QPainter::CompositionMode_DestinationIn;
76 case CompositeDestinationOut:
77 return QPainter::CompositionMode_DestinationOut;
78 case CompositeDestinationAtop:
79 return QPainter::CompositionMode_DestinationAtop;
81 return QPainter::CompositionMode_Xor;
82 case CompositePlusDarker:
83 return QPainter::CompositionMode_SourceOver;
84 case CompositeHighlight:
85 return QPainter::CompositionMode_SourceOver;
86 case CompositePlusLighter:
87 return QPainter::CompositionMode_SourceOver;
90 return QPainter::CompositionMode_SourceOver;
93 static inline Qt::PenCapStyle toQtLineCap(LineCap lc)
101 return Qt::SquareCap;
107 static inline Qt::PenJoinStyle toQtLineJoin(LineJoin lj)
111 return Qt::SvgMiterJoin;
113 return Qt::RoundJoin;
115 return Qt::BevelJoin;
118 return Qt::MiterJoin;
121 static Qt::PenStyle toQPenStyle(Pen::PenStyle style)
128 return Qt::SolidLine;
137 qWarning("couldn't recognize the pen style");
141 static inline QPen penToQPen(const Pen& pen)
143 return QPen(QColor(pen.color()), pen.width(), toQPenStyle(pen.style()));
146 struct TransparencyLayer
148 TransparencyLayer(const QPainter& p, int width, int height)
150 pixmap = QPixmap(width, height);
152 painter = new QPainter(&pixmap);
153 painter->setPen(p.pen());
154 painter->setBrush(p.brush());
155 painter->setMatrix(p.matrix());
156 painter->setOpacity(p.opacity());
157 painter->setFont(p.font());
158 painter->setCompositionMode(p.compositionMode());
159 painter->setClipPath(p.clipPath());
186 bool isNull() { return !x && !y && !blur; }
195 class GraphicsContextPlatformPrivate
198 GraphicsContextPlatformPrivate(QPainter* painter);
199 ~GraphicsContextPlatformPrivate();
203 if (layers.isEmpty()) {
209 return *layers.top().painter;
212 QPaintDevice* device;
214 QStack<TransparencyLayer> layers;
217 IntRect focusRingClip;
220 // Only used by SVG for now.
221 QPainterPath currentPath;
228 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate(QPainter* p)
229 : device(p->device())
234 // FIXME: Maybe only enable in SVG mode?
235 painter->setRenderHint(QPainter::Antialiasing);
238 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
242 GraphicsContext::GraphicsContext(PlatformGraphicsContext* context)
243 : m_common(createGraphicsContextPrivate())
244 , m_data(new GraphicsContextPlatformPrivate(context))
246 setPaintingDisabled(!context);
249 GraphicsContext::~GraphicsContext()
251 while(!m_data->layers.isEmpty())
252 endTransparencyLayer();
254 destroyGraphicsContextPrivate(m_common);
258 PlatformGraphicsContext* GraphicsContext::platformContext() const
263 void GraphicsContext::savePlatformState()
268 void GraphicsContext::restorePlatformState()
270 m_data->p().restore();
273 /* FIXME: DISABLED WHILE MERGING BACK FROM UNITY
274 void GraphicsContext::drawTextShadow(const TextRun& run, const IntPoint& point, const TextStyle& style)
276 if (paintingDisabled())
279 if (m_data->shadow.isNull())
282 TextShadow* shadow = &m_data->shadow;
284 if (shadow->blur <= 0) {
286 setPen(shadow->color);
287 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
290 const int thickness = shadow->blur;
291 // FIXME: OPTIMIZE: limit the area to only the actually painted area + 2*thickness
292 const int w = m_data->p().device()->width();
293 const int h = m_data->p().device()->height();
294 const QRgb color = qRgb(255, 255, 255);
295 const QRgb bgColor = qRgb(0, 0, 0);
296 QImage image(QSize(w, h), QImage::Format_ARGB32);
303 m_data->redirect = &p;
304 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
305 m_data->redirect = 0;
309 int md = thickness * thickness; // max-dist^2
311 // blur map/precalculated shadow-decay
312 float* bmap = (float*) alloca(sizeof(float) * (md + 1));
313 for (int n = 0; n <= md; n++) {
315 f = n / (float) (md + 1);
320 float factor = 0.0; // maximal potential opacity-sum
321 for (int n = -thickness; n <= thickness; n++) {
322 for (int m = -thickness; m <= thickness; m++) {
323 int d = n * n + m * m;
330 float* amap = (float*) alloca(sizeof(float) * (h * w));
331 memset(amap, 0, h * w * (sizeof(float)));
333 for (int j = thickness; j<h-thickness; j++) {
334 for (int i = thickness; i<w-thickness; i++) {
335 QRgb col = image.pixel(i,j);
339 float g = qAlpha(col);
342 for (int n = -thickness; n <= thickness; n++) {
343 for (int m = -thickness; m <= thickness; m++) {
344 int d = n * n + m * m;
349 amap[(i + m) + (j + n) * w] += (g * f);
355 QImage res(QSize(w,h),QImage::Format_ARGB32);
356 int r = shadow->color.red();
357 int g = shadow->color.green();
358 int b = shadow->color.blue();
359 int a1 = shadow->color.alpha();
361 // arbitratry factor adjustment to make shadows more solid.
362 factor = 1.333 / factor;
364 for (int j = 0; j < h; j++) {
365 for (int i = 0; i < w; i++) {
366 int a = (int) (amap[i + j * w] * factor * a1);
370 res.setPixel(i,j, qRgba(r, g, b, a));
374 m_data->p().drawImage(0, 0, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither);
379 // Draws a filled rectangle with a stroked border.
380 void GraphicsContext::drawRect(const IntRect& rect)
382 if (paintingDisabled())
385 m_data->p().drawRect(rect);
388 // FIXME: Now that this is refactored, it should be shared by all contexts.
389 static void adjustLineToPixelBounderies(FloatPoint& p1, FloatPoint& p2, float strokeWidth,
390 const Pen::PenStyle& penStyle)
392 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
393 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
394 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
395 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
396 if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
397 if (p1.x() == p2.x()) {
398 p1.setY(p1.y() + strokeWidth);
399 p2.setY(p2.y() - strokeWidth);
401 p1.setX(p1.x() + strokeWidth);
402 p2.setX(p2.x() - strokeWidth);
406 if (((int) strokeWidth) % 2) {
407 if (p1.x() == p2.x()) {
408 // We're a vertical line. Adjust our x.
409 p1.setX(p1.x() + 0.5);
410 p2.setX(p2.x() + 0.5);
412 // We're a horizontal line. Adjust our y.
413 p1.setY(p1.y() + 0.5);
414 p2.setY(p2.y() + 0.5);
419 // This is only used to draw borders.
420 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
422 if (paintingDisabled())
425 FloatPoint p1 = point1;
426 FloatPoint p2 = point2;
428 adjustLineToPixelBounderies(p1, p2, pen().width(), pen().style());
429 m_data->p().drawLine(p1, p2);
432 // This method is only used to draw the little circles used in lists.
433 void GraphicsContext::drawEllipse(const IntRect& rect)
435 if (paintingDisabled())
438 m_data->p().drawEllipse(rect);
441 void GraphicsContext::drawArc(const IntRect& rect, float thickness,
442 int startAngle, int angleSpan)
444 if (paintingDisabled())
447 const QPen oldPen = m_data->p().pen();
449 nPen.setWidthF(thickness);
450 m_data->p().setPen(nPen);
451 m_data->p().drawArc(rect, startAngle, angleSpan);
452 m_data->p().setPen(oldPen);
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];
469 m_data->p().setRenderHint(QPainter::Antialiasing, shouldAntialias);
470 m_data->p().drawConvexPolygon(polygon);
471 m_data->p().restore();
474 void GraphicsContext::fillRect(const IntRect& rect, const Color& c)
476 if (paintingDisabled())
479 m_data->p().fillRect(rect, QColor(c));
482 void GraphicsContext::fillRect(const FloatRect& rect, const Color& c)
484 if (paintingDisabled())
487 m_data->p().fillRect(rect, QColor(c));
490 void GraphicsContext::beginPath()
492 m_data->currentPath = QPainterPath();
495 void GraphicsContext::addPath(const Path& path)
497 m_data->currentPath = *(path.platformPath());
500 void GraphicsContext::setFillRule(WindRule rule)
502 m_data->currentPath.setFillRule(rule == RULE_EVENODD ? Qt::OddEvenFill : Qt::WindingFill);
505 PlatformPath* GraphicsContext::currentPath()
507 return &m_data->currentPath;
510 void GraphicsContext::clip(const IntRect& rect)
512 if (paintingDisabled())
516 path.addRect(QRectF(rect));
517 m_data->p().setClipPath(path, Qt::UniteClip);
520 void GraphicsContext::drawFocusRing(const Color& color)
522 if (paintingDisabled())
528 void GraphicsContext::setFocusRingClip(const IntRect& rect)
530 if (paintingDisabled())
533 m_data->focusRingClip = rect;
536 void GraphicsContext::clearFocusRingClip()
538 if (paintingDisabled())
541 m_data->focusRingClip = IntRect();
544 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset,
545 int width, bool printing)
547 if (paintingDisabled())
550 IntPoint origin = point + IntSize(0, yOffset + 1);
551 IntPoint endPoint = origin + IntSize(width, 0);
552 drawLine(origin, endPoint);
555 void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint&,
556 int width, bool grammar)
558 if (paintingDisabled())
564 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
567 rect = m_data->p().deviceMatrix().mapRect(rect);
569 QRect result = rect.toRect(); //round it
570 return FloatRect(QRectF(result));
573 void GraphicsContext::setShadow(const IntSize& pos, int blur, const Color &color)
575 if (paintingDisabled())
578 m_data->shadow.x = pos.width();
579 m_data->shadow.y = pos.height();
580 m_data->shadow.blur = blur;
581 m_data->shadow.color = color;
584 void GraphicsContext::clearShadow()
586 if (paintingDisabled())
589 m_data->shadow = TextShadow();
592 void GraphicsContext::beginTransparencyLayer(float opacity)
594 if (paintingDisabled())
597 TransparencyLayer layer(m_data->p(),
598 m_data->device->width(),
599 m_data->device->height());
601 layer.opacity = opacity;
602 m_data->layers.push(layer);
605 void GraphicsContext::endTransparencyLayer()
607 if (paintingDisabled())
610 TransparencyLayer layer = m_data->layers.pop();
611 layer.painter->end();
614 m_data->p().setOpacity(layer.opacity);
615 m_data->p().drawPixmap(0, 0, layer.pixmap);
616 m_data->p().restore();
622 void GraphicsContext::clearRect(const FloatRect& rect)
624 if (paintingDisabled())
627 m_data->p().eraseRect(rect);
630 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
632 if (paintingDisabled())
637 QPen nPen = m_data->p().pen();
638 nPen.setWidthF(width);
639 m_data->p().strokePath(path, nPen);
642 void GraphicsContext::setLineWidth(float width)
644 if (paintingDisabled())
647 QPen nPen = m_data->p().pen();
648 nPen.setWidthF(width);
649 m_data->p().setPen(nPen);
652 void GraphicsContext::setLineCap(LineCap lc)
654 if (paintingDisabled())
657 QPen nPen = m_data->p().pen();
658 nPen.setCapStyle(toQtLineCap(lc));
659 m_data->p().setPen(nPen);
662 void GraphicsContext::setLineJoin(LineJoin lj)
664 if (paintingDisabled())
667 QPen nPen = m_data->p().pen();
668 nPen.setJoinStyle(toQtLineJoin(lj));
669 m_data->p().setPen(nPen);
672 void GraphicsContext::setMiterLimit(float limit)
674 if (paintingDisabled())
677 QPen nPen = m_data->p().pen();
678 nPen.setMiterLimit(limit);
679 m_data->p().setPen(nPen);
682 void GraphicsContext::setAlpha(float opacity)
684 if (paintingDisabled())
687 m_data->p().setOpacity(opacity);
690 void GraphicsContext::setCompositeOperation(CompositeOperator op)
692 if (paintingDisabled())
695 m_data->p().setCompositionMode(toQtCompositionMode(op));
698 void GraphicsContext::clip(const Path& path)
700 if (paintingDisabled())
703 m_data->p().setClipPath(*path.platformPath());
706 void GraphicsContext::translate(float x, float y)
708 if (paintingDisabled())
711 m_data->p().translate(x, y);
714 IntPoint GraphicsContext::origin()
716 return IntPoint(qRound(m_data->p().matrix().dx()),
717 qRound(m_data->p().matrix().dy()));
720 void GraphicsContext::rotate(float radians)
722 if (paintingDisabled())
725 m_data->p().rotate(radians);
728 void GraphicsContext::scale(const FloatSize& s)
730 if (paintingDisabled())
733 m_data->p().scale(s.width(), s.height());
736 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect,
739 if (paintingDisabled())
746 path.addEllipse(QRectF(rect.x(), rect.y(), rect.width(), rect.height()));
748 // Add inner ellipse.
749 path.addEllipse(QRectF(rect.x() + thickness, rect.y() + thickness,
750 rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
752 path.setFillRule(Qt::OddEvenFill);
753 m_data->p().setClipPath(path, Qt::IntersectClip);
756 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft,
757 const IntSize& topRight, const IntSize& bottomLeft,
758 const IntSize& bottomRight)
760 if (paintingDisabled())
763 // Need sufficient width and height to contain these curves. Sanity check our top/bottom
764 // values and our width/height values to make sure the curves can all fit.
765 int requiredWidth = qMax(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
766 if (requiredWidth > rect.width())
769 int requiredHeight = qMax(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
770 if (requiredHeight > rect.height())
776 // OK, the curves can fit.
779 // Add the four ellipses to the path. Technically this really isn't good enough, since we could end up
780 // not clipping the other 3/4 of the ellipse we don't care about. We're relying on the fact that for
781 // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
782 // be subsumed by the other).
783 path.addEllipse(QRectF(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
784 path.addEllipse(QRectF(rect.right() - topRight.width() * 2, rect.y(),
785 topRight.width() * 2, topRight.height() * 2));
786 path.addEllipse(QRectF(rect.x(), rect.bottom() - bottomLeft.height() * 2,
787 bottomLeft.width() * 2, bottomLeft.height() * 2));
788 path.addEllipse(QRectF(rect.right() - bottomRight.width() * 2,
789 rect.bottom() - bottomRight.height() * 2,
790 bottomRight.width() * 2, bottomRight.height() * 2));
792 int topLeftRightHeightMax = qMax(topLeft.height(), topRight.height());
793 int bottomLeftRightHeightMax = qMax(bottomLeft.height(), bottomRight.height());
795 int topBottomLeftWidthMax = qMax(topLeft.width(), bottomLeft.width());
796 int topBottomRightWidthMax = qMax(topRight.width(), bottomRight.width());
798 // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
799 path.addRect(QRectF(rect.x() + topLeft.width(),
801 rect.width() - topLeft.width() - topRight.width(),
802 topLeftRightHeightMax));
804 path.addRect(QRectF(rect.x() + bottomLeft.width(), rect.bottom() - bottomLeftRightHeightMax,
805 rect.width() - bottomLeft.width() - bottomRight.width(), bottomLeftRightHeightMax));
807 path.addRect(QRectF(rect.x(),
808 rect.y() + topLeft.height(),
809 topBottomLeftWidthMax,
810 rect.height() - topLeft.height() - bottomLeft.height()));
812 path.addRect(QRectF(rect.right() - topBottomRightWidthMax,
813 rect.y() + topRight.height(),
814 topBottomRightWidthMax,
815 rect.height() - topRight.height() - bottomRight.height()));
817 path.addRect(QRectF(rect.x() + topBottomLeftWidthMax,
818 rect.y() + topLeftRightHeightMax,
819 rect.width() - topBottomLeftWidthMax - topBottomRightWidthMax,
820 rect.height() - topLeftRightHeightMax - bottomLeftRightHeightMax));
822 path.setFillRule(Qt::WindingFill);
823 m_data->p().setClipPath(path, Qt::IntersectClip);
826 void GraphicsContext::concatCTM(const AffineTransform& transform)
828 if (paintingDisabled())
831 m_data->p().setMatrix(transform, true);
834 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
839 void GraphicsContext::setPlatformFont(const Font& aFont)
841 m_data->p().setFont(aFont);
844 void GraphicsContext::setPlatformPen(const Pen& pen)
846 m_data->p().setPen(penToQPen(pen));
849 void GraphicsContext::setPlatformFillColor(const Color& color)
851 m_data->p().setBrush(QBrush(color));
855 GraphicsContext* contextForImage(SVGResourceImage*)