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 "GraphicsContext.h"
39 #include <QPainterPath>
40 #include <QPaintDevice>
44 #define M_PI 3.14159265358979323846
48 #include "KRenderingDeviceQt.h"
51 #define notImplemented() do { fprintf(stderr, "FIXME: UNIMPLEMENTED: %s:%d\n", __FILE__, __LINE__); } while(0)
55 static QPainter::CompositionMode toQtCompositionMode(CompositeOperator op)
59 return QPainter::CompositionMode_Clear;
61 return QPainter::CompositionMode_Source;
62 case CompositeSourceOver:
63 return QPainter::CompositionMode_SourceOver;
64 case CompositeSourceIn:
65 return QPainter::CompositionMode_SourceIn;
66 case CompositeSourceOut:
67 return QPainter::CompositionMode_SourceOut;
68 case CompositeSourceAtop:
69 return QPainter::CompositionMode_SourceAtop;
70 case CompositeDestinationOver:
71 return QPainter::CompositionMode_DestinationOver;
72 case CompositeDestinationIn:
73 return QPainter::CompositionMode_DestinationIn;
74 case CompositeDestinationOut:
75 return QPainter::CompositionMode_DestinationOut;
76 case CompositeDestinationAtop:
77 return QPainter::CompositionMode_DestinationAtop;
79 return QPainter::CompositionMode_Xor;
80 case CompositePlusDarker:
81 return QPainter::CompositionMode_SourceOver;
82 case CompositeHighlight:
83 return QPainter::CompositionMode_SourceOver;
84 case CompositePlusLighter:
85 return QPainter::CompositionMode_SourceOver;
88 return QPainter::CompositionMode_SourceOver;
91 static Qt::PenCapStyle toQtLineCap(LineCap lc)
105 static Qt::PenJoinStyle toQtLineJoin(LineJoin lj)
109 return Qt::SvgMiterJoin;
111 return Qt::RoundJoin;
113 return Qt::BevelJoin;
116 return Qt::MiterJoin;
119 struct TransparencyLayer
121 TransparencyLayer(const QPainter& p, int width, int height)
123 pixmap = QPixmap(width, height);
125 painter = new QPainter(&pixmap);
126 painter->setPen(p.pen());
127 painter->setBrush(p.brush());
128 painter->setMatrix(p.matrix());
155 bool isNull() { return !x && !y && !blur; }
164 class GraphicsContextPlatformPrivate
167 GraphicsContextPlatformPrivate(QPainter* painter);
168 ~GraphicsContextPlatformPrivate();
172 if (layers.isEmpty()) {
178 return *layers.top().painter;
181 QPaintDevice* device;
183 QStack<TransparencyLayer> layers;
186 IntRect focusRingClip;
194 GraphicsContextPlatformPrivate::GraphicsContextPlatformPrivate(QPainter* p)
195 : device(p->device())
200 // FIXME: Maybe only enable in SVG mode?
201 painter->setRenderHint(QPainter::Antialiasing);
204 GraphicsContextPlatformPrivate::~GraphicsContextPlatformPrivate()
208 GraphicsContext::GraphicsContext(PlatformGraphicsContext* context)
209 : m_common(createGraphicsContextPrivate())
210 , m_data(new GraphicsContextPlatformPrivate(context))
212 setPaintingDisabled(!context);
215 GraphicsContext::~GraphicsContext()
217 while(!m_data->layers.isEmpty())
218 endTransparencyLayer();
220 destroyGraphicsContextPrivate(m_common);
224 PlatformGraphicsContext* GraphicsContext::platformContext() const
229 void GraphicsContext::savePlatformState()
234 void GraphicsContext::restorePlatformState()
236 m_data->p().restore();
239 /* FIXME: DISABLED WHILE MERGING BACK FROM UNITY
240 void GraphicsContext::drawTextShadow(const TextRun& run, const IntPoint& point, const TextStyle& style)
242 if (paintingDisabled())
245 if (m_data->shadow.isNull())
248 TextShadow* shadow = &m_data->shadow;
250 if (shadow->blur <= 0) {
252 setPen(shadow->color);
253 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
256 const int thickness = shadow->blur;
257 // FIXME: OPTIMIZE: limit the area to only the actually painted area + 2*thickness
258 const int w = m_data->p().device()->width();
259 const int h = m_data->p().device()->height();
260 const QRgb color = qRgb(255, 255, 255);
261 const QRgb bgColor = qRgb(0, 0, 0);
262 QImage image(QSize(w, h), QImage::Format_ARGB32);
269 m_data->redirect = &p;
270 font().drawText(this, run, style, IntPoint(point.x() + shadow->x, point.y() + shadow->y));
271 m_data->redirect = 0;
275 int md = thickness * thickness; // max-dist^2
277 // blur map/precalculated shadow-decay
278 float* bmap = (float*) alloca(sizeof(float) * (md + 1));
279 for (int n = 0; n <= md; n++) {
281 f = n / (float) (md + 1);
286 float factor = 0.0; // maximal potential opacity-sum
287 for (int n = -thickness; n <= thickness; n++) {
288 for (int m = -thickness; m <= thickness; m++) {
289 int d = n * n + m * m;
296 float* amap = (float*) alloca(sizeof(float) * (h * w));
297 memset(amap, 0, h * w * (sizeof(float)));
299 for (int j = thickness; j<h-thickness; j++) {
300 for (int i = thickness; i<w-thickness; i++) {
301 QRgb col = image.pixel(i,j);
305 float g = qAlpha(col);
308 for (int n = -thickness; n <= thickness; n++) {
309 for (int m = -thickness; m <= thickness; m++) {
310 int d = n * n + m * m;
315 amap[(i + m) + (j + n) * w] += (g * f);
321 QImage res(QSize(w,h),QImage::Format_ARGB32);
322 int r = shadow->color.red();
323 int g = shadow->color.green();
324 int b = shadow->color.blue();
325 int a1 = shadow->color.alpha();
327 // arbitratry factor adjustment to make shadows more solid.
328 factor = 1.333 / factor;
330 for (int j = 0; j < h; j++) {
331 for (int i = 0; i < w; i++) {
332 int a = (int) (amap[i + j * w] * factor * a1);
336 res.setPixel(i,j, qRgba(r, g, b, a));
340 m_data->p().drawImage(0, 0, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither);
345 // Draws a filled rectangle with a stroked border.
346 void GraphicsContext::drawRect(const IntRect& rect)
348 if (paintingDisabled())
351 m_data->p().drawRect(rect);
354 // FIXME: Now that this is refactored, it should be shared by all contexts.
355 static void adjustLineToPixelBounderies(FloatPoint& p1, FloatPoint& p2, float strokeWidth,
356 const Pen::PenStyle& penStyle)
358 // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic
359 // works out. For example, with a border width of 3, KHTML will pass us (y1+y2)/2, e.g.,
360 // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave
361 // us a perfect position, but an odd width gave us a position that is off by exactly 0.5.
362 if (penStyle == Pen::DotLine || penStyle == Pen::DashLine) {
363 if (p1.x() == p2.x()) {
364 p1.setY(p1.y() + strokeWidth);
365 p2.setY(p2.y() - strokeWidth);
367 p1.setX(p1.x() + strokeWidth);
368 p2.setX(p2.x() - strokeWidth);
372 if (((int) strokeWidth) % 2) {
373 if (p1.x() == p2.x()) {
374 // We're a vertical line. Adjust our x.
375 p1.setX(p1.x() + 0.5);
376 p2.setX(p2.x() + 0.5);
378 // We're a horizontal line. Adjust our y.
379 p1.setY(p1.y() + 0.5);
380 p2.setY(p2.y() + 0.5);
385 // This is only used to draw borders.
386 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
388 if (paintingDisabled())
391 FloatPoint p1 = point1;
392 FloatPoint p2 = point2;
394 adjustLineToPixelBounderies(p1, p2, pen().width(), pen().style());
395 m_data->p().drawLine(p1, p2);
398 // This method is only used to draw the little circles used in lists.
399 void GraphicsContext::drawEllipse(const IntRect& rect)
401 if (paintingDisabled())
404 m_data->p().drawEllipse(rect);
407 void GraphicsContext::drawArc(const IntRect& rect, float thickness,
408 int startAngle, int angleSpan)
410 if (paintingDisabled())
413 const QPen oldPen = m_data->p().pen();
415 nPen.setWidthF(thickness);
416 m_data->p().setPen(nPen);
417 m_data->p().drawArc(rect, startAngle, angleSpan);
418 m_data->p().setPen(oldPen);
421 void GraphicsContext::drawConvexPolygon(size_t npoints, const FloatPoint* points, bool shouldAntialias)
423 if (paintingDisabled())
426 // FIXME: Take 'shouldAntialias' into account...
427 m_data->p().drawConvexPolygon(reinterpret_cast<const QPointF*>(points), npoints);
430 void GraphicsContext::fillRect(const IntRect& rect, const Color& c)
432 if (paintingDisabled())
435 m_data->p().fillRect(rect, QColor(c.red(), c.green(), c.blue(), c.alpha()));
438 void GraphicsContext::fillRect(const FloatRect& rect, const Color& c)
440 if (paintingDisabled())
443 m_data->p().fillRect(rect, QColor(c.red(), c.green(), c.blue(), c.alpha()));
446 void GraphicsContext::addClip(const IntRect& rect)
448 if (paintingDisabled())
452 path.addRect(QRectF(rect));
453 m_data->p().setClipPath(path, Qt::UniteClip);
456 void GraphicsContext::drawFocusRing(const Color& color)
458 if (paintingDisabled())
464 void GraphicsContext::setFocusRingClip(const IntRect& rect)
466 if (paintingDisabled())
469 m_data->focusRingClip = rect;
472 void GraphicsContext::clearFocusRingClip()
474 if (paintingDisabled())
477 m_data->focusRingClip = IntRect();
480 void GraphicsContext::drawLineForText(const IntPoint& point, int yOffset,
481 int width, bool printing)
483 if (paintingDisabled())
486 IntPoint origin = point + IntSize(0, yOffset + 1);
487 IntPoint endPoint = origin + IntSize(width, 0);
488 drawLine(origin, endPoint);
491 void GraphicsContext::drawLineForMisspelling(const IntPoint& point, int width)
493 if (paintingDisabled())
499 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& frect)
502 rect = m_data->p().deviceMatrix().mapRect(rect);
504 QRect result = rect.toRect(); //round it
505 return FloatRect(QRectF(result));
508 void GraphicsContext::setShadow(const IntSize& pos, int blur, const Color &color)
510 if (paintingDisabled())
513 m_data->shadow.x = pos.width();
514 m_data->shadow.y = pos.height();
515 m_data->shadow.blur = blur;
516 m_data->shadow.color = color;
519 void GraphicsContext::clearShadow()
521 if (paintingDisabled())
524 m_data->shadow = TextShadow();
527 void GraphicsContext::beginTransparencyLayer(float opacity)
529 if (paintingDisabled())
532 TransparencyLayer layer(m_data->p(),
533 m_data->device->width(),
534 m_data->device->height());
536 layer.opacity = opacity;
537 m_data->layers.push(layer);
540 void GraphicsContext::endTransparencyLayer()
542 if (paintingDisabled())
545 TransparencyLayer layer = m_data->layers.pop();
546 layer.painter->end();
549 m_data->p().setOpacity(layer.opacity);
550 m_data->p().drawPixmap(0, 0, layer.pixmap);
551 m_data->p().restore();
557 void GraphicsContext::clearRect(const FloatRect& rect)
559 if (paintingDisabled())
562 m_data->p().eraseRect(rect);
565 void GraphicsContext::strokeRect(const FloatRect& rect, float width)
567 if (paintingDisabled())
570 QPainterPath path; path.addRect(rect);
571 QPen nPen = m_data->p().pen();
572 nPen.setWidthF(width);
573 m_data->p().strokePath(path, nPen);
576 void GraphicsContext::setLineWidth(float width)
578 if (paintingDisabled())
581 QPen nPen = m_data->p().pen();
582 nPen.setWidthF(width);
583 m_data->p().setPen(nPen);
586 void GraphicsContext::setLineCap(LineCap lc)
588 if (paintingDisabled())
591 QPen nPen = m_data->p().pen();
592 nPen.setCapStyle(toQtLineCap(lc));
593 m_data->p().setPen(nPen);
596 void GraphicsContext::setLineJoin(LineJoin lj)
598 if (paintingDisabled())
601 QPen nPen = m_data->p().pen();
602 nPen.setJoinStyle(toQtLineJoin(lj));
603 m_data->p().setPen(nPen);
606 void GraphicsContext::setMiterLimit(float limit)
608 if (paintingDisabled())
611 QPen nPen = m_data->p().pen();
612 nPen.setMiterLimit(limit);
613 m_data->p().setPen(nPen);
616 void GraphicsContext::setAlpha(float opacity)
618 if (paintingDisabled())
621 m_data->p().setOpacity(opacity);
624 void GraphicsContext::setCompositeOperation(CompositeOperator op)
626 if (paintingDisabled())
629 m_data->p().setCompositionMode(toQtCompositionMode(op));
632 void GraphicsContext::clip(const Path& path)
634 if (paintingDisabled())
637 m_data->p().setClipPath(*path.platformPath());
640 void GraphicsContext::translate(const FloatSize& s)
642 if (paintingDisabled())
645 m_data->p().scale(s.width(), s.height());
648 void GraphicsContext::rotate(float radians)
650 if (paintingDisabled())
653 m_data->p().rotate(radians);
656 void GraphicsContext::scale(const FloatSize& s)
658 if (paintingDisabled())
661 m_data->p().scale(s.width(), s.height());
664 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect,
667 if (paintingDisabled())
674 path.addEllipse(QRectF(rect.x(), rect.y(), rect.width(), rect.height()));
676 // Add inner ellipse.
677 path.addEllipse(QRectF(rect.x() + thickness, rect.y() + thickness,
678 rect.width() - (thickness * 2), rect.height() - (thickness * 2)));
680 path.setFillRule(Qt::OddEvenFill);
681 m_data->p().setClipPath(path, Qt::IntersectClip);
684 void GraphicsContext::addRoundedRectClip(const IntRect& rect, const IntSize& topLeft,
685 const IntSize& topRight, const IntSize& bottomLeft,
686 const IntSize& bottomRight)
688 if (paintingDisabled())
691 // Need sufficient width and height to contain these curves. Sanity check our top/bottom
692 // values and our width/height values to make sure the curves can all fit.
693 int requiredWidth = qMax(topLeft.width() + topRight.width(), bottomLeft.width() + bottomRight.width());
694 if (requiredWidth > rect.width())
697 int requiredHeight = qMax(topLeft.height() + bottomLeft.height(), topRight.height() + bottomRight.height());
698 if (requiredHeight > rect.height())
704 // OK, the curves can fit.
707 // Add the four ellipses to the path. Technically this really isn't good enough, since we could end up
708 // not clipping the other 3/4 of the ellipse we don't care about. We're relying on the fact that for
709 // normal use cases these ellipses won't overlap one another (or when they do the curvature of one will
710 // be subsumed by the other).
711 path.addEllipse(QRectF(rect.x(), rect.y(), topLeft.width() * 2, topLeft.height() * 2));
712 path.addEllipse(QRectF(rect.right() - topRight.width() * 2, rect.y(),
713 topRight.width() * 2, topRight.height() * 2));
714 path.addEllipse(QRectF(rect.x(), rect.bottom() - bottomLeft.height() * 2,
715 bottomLeft.width() * 2, bottomLeft.height() * 2));
716 path.addEllipse(QRectF(rect.right() - bottomRight.width() * 2,
717 rect.bottom() - bottomRight.height() * 2,
718 bottomRight.width() * 2, bottomRight.height() * 2));
720 int topLeftRightHeightMax = qMax(topLeft.height(), topRight.height());
721 int bottomLeftRightHeightMax = qMax(bottomLeft.height(), bottomRight.height());
723 int topBottomLeftWidthMax = qMax(topLeft.width(), bottomLeft.width());
724 int topBottomRightWidthMax = qMax(topRight.width(), bottomRight.width());
726 // Now add five rects (one for each edge rect in between the rounded corners and one for the interior).
727 path.addRect(QRectF(rect.x() + topLeft.width(),
729 rect.width() - topLeft.width() - topRight.width(),
730 topLeftRightHeightMax));
732 path.addRect(QRectF(rect.x() + bottomLeft.width(), rect.bottom() - bottomLeftRightHeightMax,
733 rect.width() - bottomLeft.width() - bottomRight.width(), bottomLeftRightHeightMax));
735 path.addRect(QRectF(rect.x(),
736 rect.y() + topLeft.height(),
737 topBottomLeftWidthMax,
738 rect.height() - topLeft.height() - bottomLeft.height()));
740 path.addRect(QRectF(rect.right() - topBottomRightWidthMax,
741 rect.y() + topRight.height(),
742 topBottomRightWidthMax,
743 rect.height() - topRight.height() - bottomRight.height()));
745 path.addRect(QRectF(rect.x() + topBottomLeftWidthMax,
746 rect.y() + topLeftRightHeightMax,
747 rect.width() - topBottomLeftWidthMax - topBottomRightWidthMax,
748 rect.height() - topLeftRightHeightMax - bottomLeftRightHeightMax));
750 path.setFillRule(Qt::WindingFill);
751 m_data->p().setClipPath(path, Qt::IntersectClip);
755 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
757 return new KRenderingDeviceContextQt(platformContext());