1#define LOG_TAG "PlatformGraphicsContextSkia"
2#define LOG_NDEBUG 1
3
4#include "config.h"
5#include "PlatformGraphicsContextSkia.h"
6
7#include "AndroidLog.h"
8#include "Font.h"
9#include "GraphicsContext.h"
10#include "SkCanvas.h"
11#include "SkCornerPathEffect.h"
12#include "SkPaint.h"
13#include "SkShader.h"
14#include "SkiaUtils.h"
15
16namespace WebCore {
17
18// These are the flags we need when we call saveLayer for transparency.
19// Since it does not appear that webkit intends this to also save/restore
20// the matrix or clip, I do not give those flags (for performance)
21#define TRANSPARENCY_SAVEFLAGS                                  \
22    (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag |   \
23                          SkCanvas::kFullColorLayer_SaveFlag)
24
25//**************************************
26// Helper functions
27//**************************************
28
29static void setrectForUnderline(SkRect* r, float lineThickness,
30                                const FloatPoint& point, int yOffset, float width)
31{
32#if 0
33    if (lineThickness < 1) // Do we really need/want this?
34        lineThickness = 1;
35#endif
36    r->fLeft    = point.x();
37    r->fTop     = point.y() + yOffset;
38    r->fRight   = r->fLeft + width;
39    r->fBottom  = r->fTop + lineThickness;
40}
41
42static inline int fastMod(int value, int max)
43{
44    int sign = SkExtractSign(value);
45
46    value = SkApplySign(value, sign);
47    if (value >= max)
48        value %= max;
49    return SkApplySign(value, sign);
50}
51
52static inline void fixPaintForBitmapsThatMaySeam(SkPaint* paint) {
53    /*  Bitmaps may be drawn to seem next to other images. If we are drawn
54        zoomed, or at fractional coordinates, we may see cracks/edges if
55        we antialias, because that will cause us to draw the same pixels
56        more than once (e.g. from the left and right bitmaps that share
57        an edge).
58
59        Disabling antialiasing fixes this, and since so far we are never
60        rotated at non-multiple-of-90 angles, this seems to do no harm
61     */
62    paint->setAntiAlias(false);
63}
64
65//**************************************
66// PlatformGraphicsContextSkia
67//**************************************
68
69PlatformGraphicsContextSkia::PlatformGraphicsContextSkia(SkCanvas* canvas,
70                                                         bool takeCanvasOwnership)
71    : PlatformGraphicsContext()
72    , mCanvas(canvas)
73    , m_deleteCanvas(takeCanvasOwnership)
74{
75    m_gc = 0;
76}
77
78PlatformGraphicsContextSkia::~PlatformGraphicsContextSkia()
79{
80    if (m_deleteCanvas)
81        delete mCanvas;
82}
83
84bool PlatformGraphicsContextSkia::isPaintingDisabled()
85{
86    return !mCanvas;
87}
88
89//**************************************
90// State management
91//**************************************
92
93void PlatformGraphicsContextSkia::beginTransparencyLayer(float opacity)
94{
95    SkCanvas* canvas = mCanvas;
96    canvas->saveLayerAlpha(0, (int)(opacity * 255), TRANSPARENCY_SAVEFLAGS);
97}
98
99void PlatformGraphicsContextSkia::endTransparencyLayer()
100{
101    if (!mCanvas)
102        return;
103    mCanvas->restore();
104}
105
106void PlatformGraphicsContextSkia::save()
107{
108    PlatformGraphicsContext::save();
109    // Save our native canvas.
110    mCanvas->save();
111}
112
113void PlatformGraphicsContextSkia::restore()
114{
115    PlatformGraphicsContext::restore();
116    // Restore our native canvas.
117    mCanvas->restore();
118}
119
120//**************************************
121// Matrix operations
122//**************************************
123
124void PlatformGraphicsContextSkia::concatCTM(const AffineTransform& affine)
125{
126    mCanvas->concat(affine);
127}
128
129void PlatformGraphicsContextSkia::rotate(float angleInRadians)
130{
131    float value = angleInRadians * (180.0f / 3.14159265f);
132    mCanvas->rotate(SkFloatToScalar(value));
133}
134
135void PlatformGraphicsContextSkia::scale(const FloatSize& size)
136{
137    mCanvas->scale(SkFloatToScalar(size.width()), SkFloatToScalar(size.height()));
138}
139
140void PlatformGraphicsContextSkia::translate(float x, float y)
141{
142    mCanvas->translate(SkFloatToScalar(x), SkFloatToScalar(y));
143}
144
145const SkMatrix& PlatformGraphicsContextSkia::getTotalMatrix()
146{
147    return mCanvas->getTotalMatrix();
148}
149
150//**************************************
151// Clipping
152//**************************************
153
154void PlatformGraphicsContextSkia::addInnerRoundedRectClip(const IntRect& rect,
155                                                      int thickness)
156{
157    SkPath path;
158    SkRect r(rect);
159
160    path.addOval(r, SkPath::kCW_Direction);
161    // Only perform the inset if we won't invert r
162    if (2 * thickness < rect.width() && 2 * thickness < rect.height()) {
163        // Adding one to the thickness doesn't make the border too thick as
164        // it's painted over afterwards. But without this adjustment the
165        // border appears a little anemic after anti-aliasing.
166        r.inset(SkIntToScalar(thickness + 1), SkIntToScalar(thickness + 1));
167        path.addOval(r, SkPath::kCCW_Direction);
168    }
169    mCanvas->clipPath(path, SkRegion::kIntersect_Op, true);
170}
171
172void PlatformGraphicsContextSkia::canvasClip(const Path& path)
173{
174    clip(path);
175}
176
177bool PlatformGraphicsContextSkia::clip(const FloatRect& rect)
178{
179    return mCanvas->clipRect(rect);
180}
181
182bool PlatformGraphicsContextSkia::clip(const Path& path)
183{
184    return mCanvas->clipPath(*path.platformPath(), SkRegion::kIntersect_Op, true);
185}
186
187bool PlatformGraphicsContextSkia::clipConvexPolygon(size_t numPoints,
188                                                const FloatPoint*, bool antialias)
189{
190    if (numPoints <= 1)
191        return true;
192
193    // This is only used if HAVE_PATH_BASED_BORDER_RADIUS_DRAWING is defined
194    // in RenderObject.h which it isn't for us. TODO: Support that :)
195    return true;
196}
197
198bool PlatformGraphicsContextSkia::clipOut(const IntRect& r)
199{
200    return mCanvas->clipRect(r, SkRegion::kDifference_Op);
201}
202
203bool PlatformGraphicsContextSkia::clipOut(const Path& path)
204{
205    return mCanvas->clipPath(*path.platformPath(), SkRegion::kDifference_Op);
206}
207
208bool PlatformGraphicsContextSkia::clipPath(const Path& pathToClip, WindRule clipRule)
209{
210    SkPath path = *pathToClip.platformPath();
211    path.setFillType(clipRule == RULE_EVENODD
212            ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType);
213    return mCanvas->clipPath(path);
214}
215
216void PlatformGraphicsContextSkia::clearRect(const FloatRect& rect)
217{
218    SkPaint paint;
219
220    setupPaintFill(&paint);
221    paint.setXfermodeMode(SkXfermode::kClear_Mode);
222
223    mCanvas->drawRect(rect, paint);
224}
225
226//**************************************
227// Drawing
228//**************************************
229
230void PlatformGraphicsContextSkia::drawBitmapPattern(
231        const SkBitmap& bitmap, const SkMatrix& matrix,
232        CompositeOperator compositeOp, const FloatRect& destRect)
233{
234    SkShader* shader = SkShader::CreateBitmapShader(bitmap,
235                                                    SkShader::kRepeat_TileMode,
236                                                    SkShader::kRepeat_TileMode);
237    shader->setLocalMatrix(matrix);
238    SkPaint paint;
239    setupPaintCommon(&paint);
240    paint.setAlpha(getNormalizedAlpha());
241    paint.setShader(shader);
242    paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp));
243    fixPaintForBitmapsThatMaySeam(&paint);
244    mCanvas->drawRect(destRect, paint);
245}
246
247void PlatformGraphicsContextSkia::drawBitmapRect(const SkBitmap& bitmap,
248                                             const SkIRect* src, const SkRect& dst,
249                                             CompositeOperator op)
250{
251    SkPaint paint;
252    setupPaintCommon(&paint);
253    paint.setAlpha(getNormalizedAlpha());
254    paint.setXfermodeMode(WebCoreCompositeToSkiaComposite(op));
255    fixPaintForBitmapsThatMaySeam(&paint);
256
257    mCanvas->drawBitmapRect(bitmap, src, dst, &paint);
258}
259
260void PlatformGraphicsContextSkia::drawConvexPolygon(size_t numPoints,
261                                                const FloatPoint* points,
262                                                bool shouldAntialias)
263{
264    if (numPoints <= 1)
265        return;
266
267    SkPaint paint;
268    SkPath path;
269
270    path.incReserve(numPoints);
271    path.moveTo(SkFloatToScalar(points[0].x()), SkFloatToScalar(points[0].y()));
272    for (size_t i = 1; i < numPoints; i++)
273        path.lineTo(SkFloatToScalar(points[i].x()), SkFloatToScalar(points[i].y()));
274
275    if (mCanvas->quickReject(path, shouldAntialias ?
276            SkCanvas::kAA_EdgeType : SkCanvas::kBW_EdgeType)) {
277        return;
278    }
279
280    if (m_state->fillColor & 0xFF000000) {
281        setupPaintFill(&paint);
282        paint.setAntiAlias(shouldAntialias);
283        mCanvas->drawPath(path, paint);
284    }
285
286    if (m_state->strokeStyle != NoStroke) {
287        paint.reset();
288        setupPaintStroke(&paint, 0);
289        paint.setAntiAlias(shouldAntialias);
290        mCanvas->drawPath(path, paint);
291    }
292}
293
294void PlatformGraphicsContextSkia::drawEllipse(const IntRect& rect)
295{
296    SkPaint paint;
297    SkRect oval(rect);
298
299    if (m_state->fillColor & 0xFF000000) {
300        setupPaintFill(&paint);
301        mCanvas->drawOval(oval, paint);
302    }
303    if (m_state->strokeStyle != NoStroke) {
304        paint.reset();
305        setupPaintStroke(&paint, &oval);
306        mCanvas->drawOval(oval, paint);
307    }
308}
309
310void PlatformGraphicsContextSkia::drawFocusRing(const Vector<IntRect>& rects,
311                                            int /* width */, int /* offset */,
312                                            const Color& color)
313{
314    unsigned rectCount = rects.size();
315    if (!rectCount)
316        return;
317
318    SkRegion focusRingRegion;
319    const SkScalar focusRingOutset = WebCoreFloatToSkScalar(0.8);
320    for (unsigned i = 0; i < rectCount; i++) {
321        SkIRect r = rects[i];
322        r.inset(-focusRingOutset, -focusRingOutset);
323        focusRingRegion.op(r, SkRegion::kUnion_Op);
324    }
325
326    SkPath path;
327    SkPaint paint;
328    paint.setAntiAlias(true);
329    paint.setStyle(SkPaint::kStroke_Style);
330
331    paint.setColor(color.rgb());
332    paint.setStrokeWidth(focusRingOutset * 2);
333    paint.setPathEffect(new SkCornerPathEffect(focusRingOutset * 2))->unref();
334    focusRingRegion.getBoundaryPath(&path);
335    mCanvas->drawPath(path, paint);
336}
337
338void PlatformGraphicsContextSkia::drawHighlightForText(
339        const Font& font, const TextRun& run, const FloatPoint& point, int h,
340        const Color& backgroundColor, ColorSpace colorSpace, int from,
341        int to, bool isActive)
342{
343    IntRect rect = (IntRect)font.selectionRectForText(run, point, h, from, to);
344    if (isActive)
345        fillRect(rect, backgroundColor);
346    else {
347        int x = rect.x(), y = rect.y(), w = rect.width(), h = rect.height();
348        const int t = 3, t2 = t * 2;
349
350        fillRect(IntRect(x, y, w, t), backgroundColor);
351        fillRect(IntRect(x, y+h-t, w, t), backgroundColor);
352        fillRect(IntRect(x, y+t, t, h-t2), backgroundColor);
353        fillRect(IntRect(x+w-t, y+t, t, h-t2), backgroundColor);
354    }
355}
356
357void PlatformGraphicsContextSkia::drawLine(const IntPoint& point1,
358                                       const IntPoint& point2)
359{
360    StrokeStyle style = m_state->strokeStyle;
361    if (style == NoStroke)
362        return;
363
364    SkPaint paint;
365    SkCanvas* canvas = mCanvas;
366    const int idx = SkAbs32(point2.x() - point1.x());
367    const int idy = SkAbs32(point2.y() - point1.y());
368
369    // Special-case horizontal and vertical lines that are really just dots
370    if (setupPaintStroke(&paint, 0, !idy) && (!idx || !idy)) {
371        const SkScalar diameter = paint.getStrokeWidth();
372        const SkScalar radius = SkScalarHalf(diameter);
373        SkScalar x = SkIntToScalar(SkMin32(point1.x(), point2.x()));
374        SkScalar y = SkIntToScalar(SkMin32(point1.y(), point2.y()));
375        SkScalar dx, dy;
376        int count;
377        SkRect bounds;
378
379        if (!idy) { // Horizontal
380            bounds.set(x, y - radius, x + SkIntToScalar(idx), y + radius);
381            x += radius;
382            dx = diameter * 2;
383            dy = 0;
384            count = idx;
385        } else { // Vertical
386            bounds.set(x - radius, y, x + radius, y + SkIntToScalar(idy));
387            y += radius;
388            dx = 0;
389            dy = diameter * 2;
390            count = idy;
391        }
392
393        // The actual count is the number of ONs we hit alternating
394        // ON(diameter), OFF(diameter), ...
395        {
396            SkScalar width = SkScalarDiv(SkIntToScalar(count), diameter);
397            // Now compute the number of cells (ON and OFF)
398            count = SkScalarRound(width);
399            // Now compute the number of ONs
400            count = (count + 1) >> 1;
401        }
402
403        SkAutoMalloc storage(count * sizeof(SkPoint));
404        SkPoint* verts = (SkPoint*)storage.get();
405        // Now build the array of vertices to past to drawPoints
406        for (int i = 0; i < count; i++) {
407            verts[i].set(x, y);
408            x += dx;
409            y += dy;
410        }
411
412        paint.setStyle(SkPaint::kFill_Style);
413        paint.setPathEffect(0);
414
415        // Clipping to bounds is not required for correctness, but it does
416        // allow us to reject the entire array of points if we are completely
417        // offscreen. This is common in a webpage for android, where most of
418        // the content is clipped out. If drawPoints took an (optional) bounds
419        // parameter, that might even be better, as we would *just* use it for
420        // culling, and not both wacking the canvas' save/restore stack.
421        canvas->save(SkCanvas::kClip_SaveFlag);
422        canvas->clipRect(bounds);
423        canvas->drawPoints(SkCanvas::kPoints_PointMode, count, verts, paint);
424        canvas->restore();
425    } else {
426        SkPoint pts[2] = { point1, point2 };
427        canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
428    }
429}
430
431void PlatformGraphicsContextSkia::drawLineForText(const FloatPoint& pt, float width)
432{
433    SkRect r;
434    setrectForUnderline(&r, m_state->strokeThickness, pt, 0, width);
435
436    SkPaint paint;
437    paint.setAntiAlias(true);
438    paint.setColor(m_state->strokeColor);
439
440    mCanvas->drawRect(r, paint);
441}
442
443void PlatformGraphicsContextSkia::drawLineForTextChecking(const FloatPoint& pt,
444        float width, GraphicsContext::TextCheckingLineStyle)
445{
446    // TODO: Should we draw different based on TextCheckingLineStyle?
447    SkRect r;
448    setrectForUnderline(&r, m_state->strokeThickness, pt, 0, width);
449
450    SkPaint paint;
451    paint.setAntiAlias(true);
452    paint.setColor(SK_ColorRED); // Is this specified somewhere?
453
454    mCanvas->drawRect(r, paint);
455}
456
457void PlatformGraphicsContextSkia::drawRect(const IntRect& rect)
458{
459    SkPaint paint;
460    SkRect r(rect);
461
462    if (m_state->fillColor & 0xFF000000) {
463        setupPaintFill(&paint);
464        mCanvas->drawRect(r, paint);
465    }
466
467    // According to GraphicsContext.h, stroking inside drawRect always means
468    // a stroke of 1 inside the rect.
469    if (m_state->strokeStyle != NoStroke && (m_state->strokeColor & 0xFF000000)) {
470        paint.reset();
471        setupPaintStroke(&paint, &r);
472        paint.setPathEffect(0); // No dashing please
473        paint.setStrokeWidth(SK_Scalar1); // Always just 1.0 width
474        r.inset(SK_ScalarHalf, SK_ScalarHalf); // Ensure we're "inside"
475        mCanvas->drawRect(r, paint);
476    }
477}
478
479void PlatformGraphicsContextSkia::fillPath(const Path& pathToFill, WindRule fillRule)
480{
481    SkPath* path = pathToFill.platformPath();
482    if (!path)
483        return;
484
485    switch (fillRule) {
486    case RULE_NONZERO:
487        path->setFillType(SkPath::kWinding_FillType);
488        break;
489    case RULE_EVENODD:
490        path->setFillType(SkPath::kEvenOdd_FillType);
491        break;
492    }
493
494    SkPaint paint;
495    setupPaintFill(&paint);
496
497    mCanvas->drawPath(*path, paint);
498}
499
500void PlatformGraphicsContextSkia::fillRect(const FloatRect& rect)
501{
502    SkPaint paint;
503    setupPaintFill(&paint);
504    mCanvas->drawRect(rect, paint);
505}
506
507void PlatformGraphicsContextSkia::fillRect(const FloatRect& rect,
508                                       const Color& color)
509{
510    if (color.rgb() & 0xFF000000) {
511        SkPaint paint;
512
513        setupPaintCommon(&paint);
514        paint.setColor(color.rgb()); // Punch in the specified color
515        paint.setShader(0); // In case we had one set
516
517        // Sometimes we record and draw portions of the page, using clips
518        // for each portion. The problem with this is that webkit, sometimes,
519        // sees that we're only recording a portion, and they adjust some of
520        // their rectangle coordinates accordingly (e.g.
521        // RenderBoxModelObject::paintFillLayerExtended() which calls
522        // rect.intersect(paintInfo.rect) and then draws the bg with that
523        // rect. The result is that we end up drawing rects that are meant to
524        // seam together (one for each portion), but if the rects have
525        // fractional coordinates (e.g. we are zoomed by a fractional amount)
526        // we will double-draw those edges, resulting in visual cracks or
527        // artifacts.
528
529        // The fix seems to be to just turn off antialasing for rects (this
530        // entry-point in GraphicsContext seems to have been sufficient,
531        // though perhaps we'll find we need to do this as well in fillRect(r)
532        // as well.) Currently setupPaintCommon() enables antialiasing.
533
534        // Since we never show the page rotated at a funny angle, disabling
535        // antialiasing seems to have no real down-side, and it does fix the
536        // bug when we're zoomed (and drawing portions that need to seam).
537        paint.setAntiAlias(false);
538
539        mCanvas->drawRect(rect, paint);
540    }
541}
542
543void PlatformGraphicsContextSkia::fillRoundedRect(
544        const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
545        const IntSize& bottomLeft, const IntSize& bottomRight,
546        const Color& color)
547{
548    SkPaint paint;
549    SkPath path;
550    SkScalar radii[8];
551
552    radii[0] = SkIntToScalar(topLeft.width());
553    radii[1] = SkIntToScalar(topLeft.height());
554    radii[2] = SkIntToScalar(topRight.width());
555    radii[3] = SkIntToScalar(topRight.height());
556    radii[4] = SkIntToScalar(bottomRight.width());
557    radii[5] = SkIntToScalar(bottomRight.height());
558    radii[6] = SkIntToScalar(bottomLeft.width());
559    radii[7] = SkIntToScalar(bottomLeft.height());
560    path.addRoundRect(rect, radii);
561
562    setupPaintFill(&paint);
563    paint.setColor(color.rgb());
564    mCanvas->drawPath(path, paint);
565}
566
567void PlatformGraphicsContextSkia::strokeArc(const IntRect& r, int startAngle,
568                                        int angleSpan)
569{
570    SkPath path;
571    SkPaint paint;
572    SkRect oval(r);
573
574    if (m_state->strokeStyle == NoStroke) {
575        setupPaintFill(&paint); // We want the fill color
576        paint.setStyle(SkPaint::kStroke_Style);
577        paint.setStrokeWidth(SkFloatToScalar(m_state->strokeThickness));
578    } else
579        setupPaintStroke(&paint, 0);
580
581    // We do this before converting to scalar, so we don't overflow SkFixed
582    startAngle = fastMod(startAngle, 360);
583    angleSpan = fastMod(angleSpan, 360);
584
585    path.addArc(oval, SkIntToScalar(-startAngle), SkIntToScalar(-angleSpan));
586    mCanvas->drawPath(path, paint);
587}
588
589void PlatformGraphicsContextSkia::strokePath(const Path& pathToStroke)
590{
591    const SkPath* path = pathToStroke.platformPath();
592    if (!path)
593        return;
594
595    SkPaint paint;
596    setupPaintStroke(&paint, 0);
597
598    mCanvas->drawPath(*path, paint);
599}
600
601void PlatformGraphicsContextSkia::strokeRect(const FloatRect& rect, float lineWidth)
602{
603    SkPaint paint;
604
605    setupPaintStroke(&paint, 0);
606    paint.setStrokeWidth(SkFloatToScalar(lineWidth));
607    mCanvas->drawRect(rect, paint);
608}
609
610void PlatformGraphicsContextSkia::drawPosText(const void* text, size_t byteLength,
611                                              const SkPoint pos[], const SkPaint& paint)
612{
613    mCanvas->drawPosText(text, byteLength, pos, paint);
614}
615
616void PlatformGraphicsContextSkia::drawMediaButton(const IntRect& rect, RenderSkinMediaButton::MediaButton buttonType,
617                                                  bool translucent, bool drawBackground,
618                                                  const IntRect& thumb)
619{
620    RenderSkinMediaButton::Draw(mCanvas, rect, buttonType, translucent, drawBackground, thumb);
621}
622
623}   // WebCore
624