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.
37 #include "GraphicsContext.h"
42 #include <QPainterPath>
43 #include <QPaintDevice>
46 #define M_PI 3.14159265358979323846
50 #include "KRenderingDeviceQt.h"
53 #define notImplemented() do { fprintf(stderr, "FIXME: UNIMPLEMENTED: %s:%d\n", __FILE__, __LINE__); } while(0)
57 static 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 Qt::PenCapStyle toQtLineCap(LineCap lc)
101 return Qt::SquareCap;
107 static Qt::PenJoinStyle toQtLineJoin(LineJoin lj)
111 return Qt::SvgMiterJoin;
113 return Qt::RoundJoin;
115 return Qt::BevelJoin;
118 return Qt::MiterJoin;
121 struct TransparencyLayer
123 TransparencyLayer(const QPainter& p, int width, int height)
125 pixmap = QPixmap(width, height);
127 painter = new QPainter(&pixmap);
128 painter->setPen(p.pen());
129 painter->setBrush(p.brush());
130 painter->setMatrix(p.matrix());
157 bool isNull() { return !x && !y && !blur; }
166 class GraphicsContextPlatformPrivate
169 GraphicsContextPlatformPrivate(QPainter* painter);
170 ~GraphicsContextPlatformPrivate();
174 if (layers.isEmpty()) {
180 return *layers.top().painter;
183 QPaintDevice* device;
185 QStack<TransparencyLayer> layers;
188 IntRect focusRingClip;
196 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate(QPainter* p)
197 : device(p->device())
202 // FIXME: Maybe only enable in SVG mode?
203 painter->setRenderHint(QPainter::Antialiasing);
206 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
210 GraphicsContext::GraphicsContext(PlatformGraphicsContext* context)
211 : m_common(createGraphicsContextPrivate())
212 , m_data(new GraphicsContextPlatformPrivate(context))
214 setPaintingDisabled(!context);
217 GraphicsContext::~GraphicsContext()
219 while(!m_data->layers.isEmpty())
220 endTransparencyLayer();
222 destroyGraphicsContextPrivate(m_common);
226 PlatformGraphicsContext* GraphicsContext::platformContext() const
231 void GraphicsContext::savePlatformState()
236 void GraphicsContext::restorePlatformState()
238 m_data->p().restore();
241 /* FIXME: DISABLED WHILE MERGING BACK FROM UNITY
242 void GraphicsContext::drawTextShadow(const TextRun& run, const IntPoint& point, const TextStyle& style)
244 if (paintingDisabled())
247 if (m_data->shadow.isNull())
250 TextShadow* shadow = &m_data->shadow;
252 if (shadow->blur <= 0) {
254 setPen(shadow->color);
255 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
258 const int thickness = shadow->blur;
259 // FIXME: OPTIMIZE: limit the area to only the actually painted area + 2*thickness
260 const int w = m_data->p().device()->width();
261 const int h = m_data->p().device()->height();
262 const QRgb color = qRgb(255, 255, 255);
263 const QRgb bgColor = qRgb(0, 0, 0);
264 QImage image(QSize(w, h), QImage::Format_ARGB32);
271 m_data->redirect = &p;
272 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
273 m_data->redirect = 0;
277 int md = thickness * thickness; // max-dist^2
279 // blur map/precalculated shadow-decay
280 float* bmap = (float*) alloca(sizeof(float) * (md + 1));
281 for (int n = 0; n <= md; n++) {
283 f = n / (float) (md + 1);
288 float factor = 0.0; // maximal potential opacity-sum
289 for (int n = -thickness; n <= thickness; n++) {
290 for (int m = -thickness; m <= thickness; m++) {
291 int d = n * n + m * m;
298 float* amap = (float*) alloca(sizeof(float) * (h * w));
299 memset(amap, 0, h * w * (sizeof(float)));
301 for (int j = thickness; j<h-thickness; j++) {
302 for (int i = thickness; i<w-thickness; i++) {
303 QRgb col = image.pixel(i,j);
307 float g = qAlpha(col);
310 for (int n = -thickness; n <= thickness; n++) {
311 for (int m = -thickness; m <= thickness; m++) {
312 int d = n * n + m * m;
317 amap[(i + m) + (j + n) * w] += (g * f);
323 QImage res(QSize(w,h),QImage::Format_ARGB32);
324 int r = shadow->color.red();
325 int g = shadow->color.green();
326 int b = shadow->color.blue();
327 int a1 = shadow->color.alpha();
329 // arbitratry factor adjustment to make shadows more solid.
330 factor = 1.333 / factor;
332 for (int j = 0; j < h; j++) {
333 for (int i = 0; i < w; i++) {
334 int a = (int) (amap[i + j * w] * factor * a1);
338 res.setPixel(i,j, qRgba(r, g, b, a));
342 m_data->p().drawImage(0, 0, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither);
347 // Draws a filled rectangle with a stroked border.
348 void GraphicsContext::drawRect(const IntRect& rect)
350 if (paintingDisabled())
353 m_data->p().drawRect(rect);
356 // FIXME: Now that this is refactored, it should be shared by all contexts.
357 static void adjustLineToPixelBounderies(FloatPoint& p1, FloatPoint& p2, float strokeWidth,
358 const Pen::PenStyle& penStyle)
360 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
361 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
362 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
363 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
364 if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
365 if (p1.x() == p2.x()) {
366 p1.setY(p1.y() + strokeWidth);
367 p2.setY(p2.y() - strokeWidth);
369 p1.setX(p1.x() + strokeWidth);
370 p2.setX(p2.x() - strokeWidth);
374 if (((int) strokeWidth) % 2) {
375 if (p1.x() == p2.x()) {
376 // We're a vertical line. Adjust our x.
377 p1.setX(p1.x() + 0.5);
378 p2.setX(p2.x() + 0.5);
380 // We're a horizontal line. Adjust our y.
381 p1.setY(p1.y() + 0.5);
382 p2.setY(p2.y() + 0.5);
387 // This is only used to draw borders.
388 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
390 if (paintingDisabled())
393 FloatPoint p1 = point1;
394 FloatPoint p2 = point2;
396 adjustLineToPixelBounderies(p1, p2, pen().width(), pen().style());
397 m_data->p().drawLine(p1, p2);
400 // This method is only used to draw the little circles used in lists.
401 void GraphicsContext::drawEllipse(const IntRect& rect)
403 if (paintingDisabled())
406 m_data->p().drawEllipse(rect);
409 void GraphicsContext::drawArc(const IntRect& rect, float thickness,
410 int startAngle, int angleSpan)
412 if (paintingDisabled())
415 const QPen oldPen = m_data->p().pen();
417 nPen.setWidthF(thickness);
418 m_data->p().setPen(nPen);
419 m_data->p().drawArc(rect, startAngle, angleSpan);
420 m_data->p().setPen(oldPen);
423 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
425 if (paintingDisabled())
431 QPolygonF polygon(npoints);
433 for (size_t i = 0; i < npoints; i++)
434 polygon << points[i];
437 m_data->p().setRenderHint(QPainter::Antialiasing, shouldAntialias);
438 m_data->p().drawConvexPolygon(polygon);
439 m_data->p().restore();
442 void GraphicsContext::fillRect(const IntRect& rect, const Color& c)
444 if (paintingDisabled())
447 m_data->p().fillRect(rect, QColor(c));
450 void GraphicsContext::fillRect(const FloatRect& rect, const Color& c)
452 if (paintingDisabled())
455 m_data->p().fillRect(rect, QColor(c));
458 void GraphicsContext::clip(const IntRect& rect)
460 if (paintingDisabled())
464 path.addRect(QRectF(rect));
465 m_data->p().setClipPath(path, Qt::UniteClip);
468 void GraphicsContext::drawFocusRing(const Color& color)
470 if (paintingDisabled())
476 void GraphicsContext::setFocusRingClip(const IntRect& rect)
478 if (paintingDisabled())
481 m_data->focusRingClip = rect;
484 void GraphicsContext::clearFocusRingClip()
486 if (paintingDisabled())
489 m_data->focusRingClip = IntRect();
492 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset,
493 int width, bool printing)
495 if (paintingDisabled())
498 IntPoint origin = point + IntSize(0, yOffset + 1);
499 IntPoint endPoint = origin + IntSize(width, 0);
500 drawLine(origin, endPoint);
503 void GraphicsContext::drawLineForMisspelling(const IntPoint& point, int width)
505 if (paintingDisabled())
511 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
514 rect = m_data->p().deviceMatrix().mapRect(rect);
516 QRect result = rect.toRect(); //round it
517 return FloatRect(QRectF(result));
520 void GraphicsContext::setShadow(const IntSize& pos, int blur, const Color &color)
522 if (paintingDisabled())
525 m_data->shadow.x = pos.width();
526 m_data->shadow.y = pos.height();
527 m_data->shadow.blur = blur;
528 m_data->shadow.color = color;
531 void GraphicsContext::clearShadow()
533 if (paintingDisabled())
536 m_data->shadow = TextShadow();
539 void GraphicsContext::beginTransparencyLayer(float opacity)
541 if (paintingDisabled())
544 TransparencyLayer layer(m_data->p(),
545 m_data->device->width(),
546 m_data->device->height());
548 layer.opacity = opacity;
549 m_data->layers.push(layer);
552 void GraphicsContext::endTransparencyLayer()
554 if (paintingDisabled())
557 TransparencyLayer layer = m_data->layers.pop();
558 layer.painter->end();
561 m_data->p().setOpacity(layer.opacity);
562 m_data->p().drawPixmap(0, 0, layer.pixmap);
563 m_data->p().restore();
569 void GraphicsContext::clearRect(const FloatRect& rect)
571 if (paintingDisabled())
574 m_data->p().eraseRect(rect);
577 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
579 if (paintingDisabled())
582 QPainterPath path; path.addRect(rect);
583 QPen nPen = m_data->p().pen();
584 nPen.setWidthF(width);
585 m_data->p().strokePath(path, nPen);
588 void GraphicsContext::setLineWidth(float width)
590 if (paintingDisabled())
593 QPen nPen = m_data->p().pen();
594 nPen.setWidthF(width);
595 m_data->p().setPen(nPen);
598 void GraphicsContext::setLineCap(LineCap lc)
600 if (paintingDisabled())
603 QPen nPen = m_data->p().pen();
604 nPen.setCapStyle(toQtLineCap(lc));
605 m_data->p().setPen(nPen);
608 void GraphicsContext::setLineJoin(LineJoin lj)
610 if (paintingDisabled())
613 QPen nPen = m_data->p().pen();
614 nPen.setJoinStyle(toQtLineJoin(lj));
615 m_data->p().setPen(nPen);
618 void GraphicsContext::setMiterLimit(float limit)
620 if (paintingDisabled())
623 QPen nPen = m_data->p().pen();
624 nPen.setMiterLimit(limit);
625 m_data->p().setPen(nPen);
628 void GraphicsContext::setAlpha(float opacity)
630 if (paintingDisabled())
633 m_data->p().setOpacity(opacity);
636 void GraphicsContext::setCompositeOperation(CompositeOperator op)
638 if (paintingDisabled())
641 m_data->p().setCompositionMode(toQtCompositionMode(op));
644 void GraphicsContext::clip(const Path& path)
646 if (paintingDisabled())
649 m_data->p().setClipPath(*path.platformPath());
652 void GraphicsContext::translate(float x, float y)
654 if (paintingDisabled())
657 m_data->p().translate(x, y);
660 IntPoint GraphicsContext::origin()
662 return IntPoint(qRound(m_data->p().matrix().dx()),
663 qRound(m_data->p().matrix().dy()));
666 void GraphicsContext::rotate(float radians)
668 if (paintingDisabled())
671 m_data->p().rotate(radians);
674 void GraphicsContext::scale(const FloatSize& s)
676 if (paintingDisabled())
679 m_data->p().scale(s.width(), s.height());
682 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect,
685 if (paintingDisabled())
692 path.addEllipse(QRectF(rect.x(), rect.y(), rect.width(), rect.height()));
694 // Add inner ellipse.
695 path.addEllipse(QRectF(rect.x() + thickness, rect.y() + thickness,
696 rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
698 path.setFillRule(Qt::OddEvenFill);
699 m_data->p().setClipPath(path, Qt::IntersectClip);
702 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft,
703 const IntSize& topRight, const IntSize& bottomLeft,
704 const IntSize& bottomRight)
706 if (paintingDisabled())
709 // Need sufficient width and height to contain these curves. Sanity check our top/bottom
710 // values and our width/height values to make sure the curves can all fit.
711 int requiredWidth = qMax(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
712 if (requiredWidth > rect.width())
715 int requiredHeight = qMax(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
716 if (requiredHeight > rect.height())
722 // OK, the curves can fit.
725 // Add the four ellipses to the path. Technically this really isn't good enough, since we could end up
726 // not clipping the other 3/4 of the ellipse we don't care about. We're relying on the fact that for
727 // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
728 // be subsumed by the other).
729 path.addEllipse(QRectF(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
730 path.addEllipse(QRectF(rect.right() - topRight.width() * 2, rect.y(),
731 topRight.width() * 2, topRight.height() * 2));
732 path.addEllipse(QRectF(rect.x(), rect.bottom() - bottomLeft.height() * 2,
733 bottomLeft.width() * 2, bottomLeft.height() * 2));
734 path.addEllipse(QRectF(rect.right() - bottomRight.width() * 2,
735 rect.bottom() - bottomRight.height() * 2,
736 bottomRight.width() * 2, bottomRight.height() * 2));
738 int topLeftRightHeightMax = qMax(topLeft.height(), topRight.height());
739 int bottomLeftRightHeightMax = qMax(bottomLeft.height(), bottomRight.height());
741 int topBottomLeftWidthMax = qMax(topLeft.width(), bottomLeft.width());
742 int topBottomRightWidthMax = qMax(topRight.width(), bottomRight.width());
744 // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
745 path.addRect(QRectF(rect.x() + topLeft.width(),
747 rect.width() - topLeft.width() - topRight.width(),
748 topLeftRightHeightMax));
750 path.addRect(QRectF(rect.x() + bottomLeft.width(), rect.bottom() - bottomLeftRightHeightMax,
751 rect.width() - bottomLeft.width() - bottomRight.width(), bottomLeftRightHeightMax));
753 path.addRect(QRectF(rect.x(),
754 rect.y() + topLeft.height(),
755 topBottomLeftWidthMax,
756 rect.height() - topLeft.height() - bottomLeft.height()));
758 path.addRect(QRectF(rect.right() - topBottomRightWidthMax,
759 rect.y() + topRight.height(),
760 topBottomRightWidthMax,
761 rect.height() - topRight.height() - bottomRight.height()));
763 path.addRect(QRectF(rect.x() + topBottomLeftWidthMax,
764 rect.y() + topLeftRightHeightMax,
765 rect.width() - topBottomLeftWidthMax - topBottomRightWidthMax,
766 rect.height() - topLeftRightHeightMax - bottomLeftRightHeightMax));
768 path.setFillRule(Qt::WindingFill);
769 m_data->p().setClipPath(path, Qt::IntersectClip);
772 void GraphicsContext::concatCTM(const AffineTransform& transform)
774 if (paintingDisabled())
777 m_data->p().setMatrix(transform, true);
781 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
783 return new KRenderingDeviceContextQt(platformContext());