1 /*
2 * Copyright 2006, The Android Open Source Project
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27 #include "GraphicsContext.h"
28
29 #include "AffineTransform.h"
30 #include "Gradient.h"
31 #include "GraphicsContextPrivate.h"
32 #include "NotImplemented.h"
33 #include "Path.h"
34 #include "Pattern.h"
35 #include "PlatformGraphicsContext.h"
36 #include "SkBitmapRef.h"
37 #include "SkBlurDrawLooper.h"
38 #include "SkBlurMaskFilter.h"
39 #include "SkCanvas.h"
40 #include "SkColorPriv.h"
41 #include "SkDashPathEffect.h"
42 #include "SkDevice.h"
43 #include "SkGradientShader.h"
44 #include "SkPaint.h"
45 #include "SkString.h"
46 #include "SkiaUtils.h"
47 #include "TransformationMatrix.h"
48 #include "android_graphics.h"
49
50 using namespace std;
51
52 #define GC2Canvas(ctx) (ctx)->m_data->mPgc->mCanvas
53
54 namespace WebCore {
55
RoundToInt(float x)56 static int RoundToInt(float x)
57 {
58 return (int)roundf(x);
59 }
60
deepCopyPtr(const T * src)61 template <typename T> T* deepCopyPtr(const T* src) {
62 return src ? new T(*src) : NULL;
63 }
64
65 /* TODO / questions
66
67 mAlpha: how does this interact with the alpha in Color? multiply them together?
68 mMode: do I always respect this? If so, then
69 the rgb() & 0xFF000000 check will abort drawing too often
70 Is Color premultiplied or not? If it is, then I can't blindly pass it to paint.setColor()
71 */
72
73 struct ShadowRec {
74 SkScalar mBlur; // >0 means valid shadow
75 SkScalar mDx;
76 SkScalar mDy;
77 SkColor mColor;
78 };
79
80 class GraphicsContextPlatformPrivate {
81 public:
82 GraphicsContext* mCG; // back-ptr to our parent
83 PlatformGraphicsContext* mPgc;
84
85 struct State {
86 SkPath* mPath;
87 SkPathEffect* mPathEffect;
88 float mMiterLimit;
89 float mAlpha;
90 float mStrokeThickness;
91 SkPaint::Cap mLineCap;
92 SkPaint::Join mLineJoin;
93 SkXfermode::Mode mMode;
94 int mDashRatio; //ratio of the length of a dash to its width
95 ShadowRec mShadow;
96 SkColor mFillColor;
97 SkColor mStrokeColor;
98 bool mUseAA;
99
StateWebCore::GraphicsContextPlatformPrivate::State100 State() {
101 mPath = NULL; // lazily allocated
102 mPathEffect = 0;
103 mMiterLimit = 4;
104 mAlpha = 1;
105 mStrokeThickness = 0.0f; // Same as default in GraphicsContextPrivate.h
106 mLineCap = SkPaint::kDefault_Cap;
107 mLineJoin = SkPaint::kDefault_Join;
108 mMode = SkXfermode::kSrcOver_Mode;
109 mDashRatio = 3;
110 mUseAA = true;
111 mShadow.mBlur = 0;
112 mFillColor = SK_ColorBLACK;
113 mStrokeColor = SK_ColorBLACK;
114 }
115
StateWebCore::GraphicsContextPlatformPrivate::State116 State(const State& other) {
117 memcpy(this, &other, sizeof(State));
118 mPath = deepCopyPtr<SkPath>(other.mPath);
119 mPathEffect->safeRef();
120 }
121
~StateWebCore::GraphicsContextPlatformPrivate::State122 ~State() {
123 delete mPath;
124 mPathEffect->safeUnref();
125 }
126
setShadowWebCore::GraphicsContextPlatformPrivate::State127 void setShadow(int radius, int dx, int dy, SkColor c) {
128 // cut the radius in half, to visually match the effect seen in
129 // safari browser
130 mShadow.mBlur = SkScalarHalf(SkIntToScalar(radius));
131 mShadow.mDx = SkIntToScalar(dx);
132 mShadow.mDy = SkIntToScalar(dy);
133 mShadow.mColor = c;
134 }
135
setupShadowPaintWebCore::GraphicsContextPlatformPrivate::State136 bool setupShadowPaint(SkPaint* paint, SkPoint* offset) {
137 if (mShadow.mBlur > 0) {
138 paint->setAntiAlias(true);
139 paint->setDither(true);
140 paint->setXfermodeMode(mMode);
141 paint->setColor(mShadow.mColor);
142 paint->setMaskFilter(SkBlurMaskFilter::Create(mShadow.mBlur,
143 SkBlurMaskFilter::kNormal_BlurStyle))->unref();
144 offset->set(mShadow.mDx, mShadow.mDy);
145 return true;
146 }
147 return false;
148 }
149
applyAlphaWebCore::GraphicsContextPlatformPrivate::State150 SkColor applyAlpha(SkColor c) const
151 {
152 int s = RoundToInt(mAlpha * 256);
153 if (s >= 256)
154 return c;
155 if (s < 0)
156 return 0;
157
158 int a = SkAlphaMul(SkColorGetA(c), s);
159 return (c & 0x00FFFFFF) | (a << 24);
160 }
161 };
162
163 SkDeque mStateStack;
164 State* mState;
165
GraphicsContextPlatformPrivate(GraphicsContext * cg,PlatformGraphicsContext * pgc)166 GraphicsContextPlatformPrivate(GraphicsContext* cg, PlatformGraphicsContext* pgc)
167 : mCG(cg)
168 , mPgc(pgc), mStateStack(sizeof(State)) {
169 State* state = (State*)mStateStack.push_back();
170 new (state) State();
171 mState = state;
172 }
173
~GraphicsContextPlatformPrivate()174 ~GraphicsContextPlatformPrivate() {
175 if (mPgc && mPgc->deleteUs())
176 delete mPgc;
177
178 // we force restores so we don't leak any subobjects owned by our
179 // stack of State records.
180 while (mStateStack.count() > 0)
181 this->restore();
182 }
183
save()184 void save() {
185 State* newState = (State*)mStateStack.push_back();
186 new (newState) State(*mState);
187 mState = newState;
188 }
189
restore()190 void restore() {
191 mState->~State();
192 mStateStack.pop_back();
193 mState = (State*)mStateStack.back();
194 }
195
setFillColor(const Color & c)196 void setFillColor(const Color& c) {
197 mState->mFillColor = c.rgb();
198 }
199
setStrokeColor(const Color & c)200 void setStrokeColor(const Color& c) {
201 mState->mStrokeColor = c.rgb();
202 }
203
setStrokeThickness(float f)204 void setStrokeThickness(float f) {
205 mState->mStrokeThickness = f;
206 }
207
beginPath()208 void beginPath() {
209 if (mState->mPath) {
210 mState->mPath->reset();
211 }
212 }
213
addPath(const SkPath & other)214 void addPath(const SkPath& other) {
215 if (!mState->mPath) {
216 mState->mPath = new SkPath(other);
217 } else {
218 mState->mPath->addPath(other);
219 }
220 }
221
222 // may return null
getPath() const223 SkPath* getPath() const { return mState->mPath; }
224
setup_paint_common(SkPaint * paint) const225 void setup_paint_common(SkPaint* paint) const {
226 paint->setAntiAlias(mState->mUseAA);
227 paint->setDither(true);
228 paint->setXfermodeMode(mState->mMode);
229 if (mState->mShadow.mBlur > 0) {
230 SkDrawLooper* looper = new SkBlurDrawLooper(mState->mShadow.mBlur,
231 mState->mShadow.mDx,
232 mState->mShadow.mDy,
233 mState->mShadow.mColor);
234 paint->setLooper(looper)->unref();
235 }
236
237 /* need to sniff textDrawingMode(), which returns the bit_OR of...
238 const int cTextInvisible = 0;
239 const int cTextFill = 1;
240 const int cTextStroke = 2;
241 const int cTextClip = 4;
242 */
243 }
244
setup_paint_fill(SkPaint * paint) const245 void setup_paint_fill(SkPaint* paint) const {
246 this->setup_paint_common(paint);
247 paint->setColor(mState->applyAlpha(mState->mFillColor));
248 }
249
setup_paint_bitmap(SkPaint * paint) const250 void setup_paint_bitmap(SkPaint* paint) const {
251 this->setup_paint_common(paint);
252 // we only want the global alpha for bitmaps,
253 // so just give applyAlpha opaque black
254 paint->setColor(mState->applyAlpha(0xFF000000));
255 }
256
257 /* sets up the paint for stroking. Returns true if the style is really
258 just a dash of squares (the size of the paint's stroke-width.
259 */
setup_paint_stroke(SkPaint * paint,SkRect * rect)260 bool setup_paint_stroke(SkPaint* paint, SkRect* rect) {
261 this->setup_paint_common(paint);
262 paint->setColor(mState->applyAlpha(mState->mStrokeColor));
263
264 float width = mState->mStrokeThickness;
265
266 // this allows dashing and dotting to work properly for hairline strokes
267 // FIXME: Should we only do this for dashed and dotted strokes?
268 if (0 == width) {
269 width = 1;
270 }
271
272 // paint->setColor(mState->applyAlpha(mCG->strokeColor().rgb()));
273 paint->setStyle(SkPaint::kStroke_Style);
274 paint->setStrokeWidth(SkFloatToScalar(width));
275 paint->setStrokeCap(mState->mLineCap);
276 paint->setStrokeJoin(mState->mLineJoin);
277 paint->setStrokeMiter(SkFloatToScalar(mState->mMiterLimit));
278
279 if (rect != NULL && (RoundToInt(width) & 1)) {
280 rect->inset(-SK_ScalarHalf, -SK_ScalarHalf);
281 }
282
283 SkPathEffect* pe = mState->mPathEffect;
284 if (pe) {
285 paint->setPathEffect(pe);
286 return false;
287 }
288 switch (mCG->strokeStyle()) {
289 case NoStroke:
290 case SolidStroke:
291 width = 0;
292 break;
293 case DashedStroke:
294 width = mState->mDashRatio * width;
295 break;
296 /* no break */
297 case DottedStroke:
298 break;
299 }
300
301 if (width > 0) {
302 SkScalar intervals[] = { width, width };
303 pe = new SkDashPathEffect(intervals, 2, 0);
304 paint->setPathEffect(pe)->unref();
305 // return true if we're basically a dotted dash of squares
306 return RoundToInt(width) == RoundToInt(paint->getStrokeWidth());
307 }
308 return false;
309 }
310
311 private:
312 // not supported yet
313 State& operator=(const State&);
314 };
315
SpreadMethod2TileMode(GradientSpreadMethod sm)316 static SkShader::TileMode SpreadMethod2TileMode(GradientSpreadMethod sm) {
317 SkShader::TileMode mode = SkShader::kClamp_TileMode;
318
319 switch (sm) {
320 case SpreadMethodPad:
321 mode = SkShader::kClamp_TileMode;
322 break;
323 case SpreadMethodReflect:
324 mode = SkShader::kMirror_TileMode;
325 break;
326 case SpreadMethodRepeat:
327 mode = SkShader::kRepeat_TileMode;
328 break;
329 }
330 return mode;
331 }
332
extactShader(SkPaint * paint,Pattern * pat,Gradient * grad)333 static void extactShader(SkPaint* paint, Pattern* pat, Gradient* grad)
334 {
335 if (pat) {
336 // platformPattern() returns a cached obj
337 paint->setShader(pat->platformPattern(AffineTransform()));
338 } else if (grad) {
339 // grad->getShader() returns a cached obj
340 GradientSpreadMethod sm = grad->spreadMethod();
341 paint->setShader(grad->getShader(SpreadMethod2TileMode(sm)));
342 }
343 }
344
345 ////////////////////////////////////////////////////////////////////////////////////////////////
346
createOffscreenContext(int width,int height)347 GraphicsContext* GraphicsContext::createOffscreenContext(int width, int height)
348 {
349 PlatformGraphicsContext* pgc = new PlatformGraphicsContext();
350
351 SkBitmap bitmap;
352
353 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
354 bitmap.allocPixels();
355 bitmap.eraseColor(0);
356 pgc->mCanvas->setBitmapDevice(bitmap);
357
358 GraphicsContext* ctx = new GraphicsContext(pgc);
359 //printf("-------- create offscreen <canvas> %p\n", ctx);
360 return ctx;
361 }
362
363 ////////////////////////////////////////////////////////////////////////////////////////////////
364
GraphicsContext(PlatformGraphicsContext * gc)365 GraphicsContext::GraphicsContext(PlatformGraphicsContext *gc)
366 : m_common(createGraphicsContextPrivate())
367 , m_data(new GraphicsContextPlatformPrivate(this, gc))
368 {
369 setPaintingDisabled(NULL == gc || NULL == gc->mCanvas);
370 }
371
~GraphicsContext()372 GraphicsContext::~GraphicsContext()
373 {
374 delete m_data;
375 this->destroyGraphicsContextPrivate(m_common);
376 }
377
savePlatformState()378 void GraphicsContext::savePlatformState()
379 {
380 // save our private State
381 m_data->save();
382 // save our native canvas
383 GC2Canvas(this)->save();
384 }
385
restorePlatformState()386 void GraphicsContext::restorePlatformState()
387 {
388 // restore our native canvas
389 GC2Canvas(this)->restore();
390 // restore our private State
391 m_data->restore();
392 }
393
willFill() const394 bool GraphicsContext::willFill() const {
395 return m_data->mState->mFillColor != 0;
396 }
397
willStroke() const398 bool GraphicsContext::willStroke() const {
399 return m_data->mState->mStrokeColor != 0;
400 }
401
getCurrPath() const402 const SkPath* GraphicsContext::getCurrPath() const {
403 return m_data->mState->mPath;
404 }
405
406 // Draws a filled rectangle with a stroked border.
drawRect(const IntRect & rect)407 void GraphicsContext::drawRect(const IntRect& rect)
408 {
409 if (paintingDisabled())
410 return;
411
412 SkPaint paint;
413 SkRect r(rect);
414
415 if (fillColor().alpha()) {
416 m_data->setup_paint_fill(&paint);
417 GC2Canvas(this)->drawRect(r, paint);
418 }
419
420 /* According to GraphicsContext.h, stroking inside drawRect always means
421 a stroke of 1 inside the rect.
422 */
423 if (strokeStyle() != NoStroke && strokeColor().alpha()) {
424 paint.reset();
425 m_data->setup_paint_stroke(&paint, &r);
426 paint.setPathEffect(NULL); // no dashing please
427 paint.setStrokeWidth(SK_Scalar1); // always just 1.0 width
428 r.inset(SK_ScalarHalf, SK_ScalarHalf); // ensure we're "inside"
429 GC2Canvas(this)->drawRect(r, paint);
430 }
431 }
432
433 // This is only used to draw borders.
drawLine(const IntPoint & point1,const IntPoint & point2)434 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
435 {
436 if (paintingDisabled())
437 return;
438
439 StrokeStyle style = strokeStyle();
440 if (style == NoStroke)
441 return;
442
443 SkPaint paint;
444 SkCanvas* canvas = GC2Canvas(this);
445 const int idx = SkAbs32(point2.x() - point1.x());
446 const int idy = SkAbs32(point2.y() - point1.y());
447
448 // special-case horizontal and vertical lines that are really just dots
449 if (m_data->setup_paint_stroke(&paint, NULL) && (0 == idx || 0 == idy)) {
450 const SkScalar diameter = paint.getStrokeWidth();
451 const SkScalar radius = SkScalarHalf(diameter);
452 SkScalar x = SkIntToScalar(SkMin32(point1.x(), point2.x()));
453 SkScalar y = SkIntToScalar(SkMin32(point1.y(), point2.y()));
454 SkScalar dx, dy;
455 int count;
456 SkRect bounds;
457
458 if (0 == idy) { // horizontal
459 bounds.set(x, y - radius, x + SkIntToScalar(idx), y + radius);
460 x += radius;
461 dx = diameter * 2;
462 dy = 0;
463 count = idx;
464 } else { // vertical
465 bounds.set(x - radius, y, x + radius, y + SkIntToScalar(idy));
466 y += radius;
467 dx = 0;
468 dy = diameter * 2;
469 count = idy;
470 }
471
472 // the actual count is the number of ONs we hit alternating
473 // ON(diameter), OFF(diameter), ...
474 {
475 SkScalar width = SkScalarDiv(SkIntToScalar(count), diameter);
476 // now computer the number of cells (ON and OFF)
477 count = SkScalarRound(width);
478 // now compute the number of ONs
479 count = (count + 1) >> 1;
480 }
481
482 SkAutoMalloc storage(count * sizeof(SkPoint));
483 SkPoint* verts = (SkPoint*)storage.get();
484 // now build the array of vertices to past to drawPoints
485 for (int i = 0; i < count; i++) {
486 verts[i].set(x, y);
487 x += dx;
488 y += dy;
489 }
490
491 paint.setStyle(SkPaint::kFill_Style);
492 paint.setPathEffect(NULL);
493
494 // clipping to bounds is not required for correctness, but it does
495 // allow us to reject the entire array of points if we are completely
496 // offscreen. This is common in a webpage for android, where most of
497 // the content is clipped out. If drawPoints took an (optional) bounds
498 // parameter, that might even be better, as we would *just* use it for
499 // culling, and not both wacking the canvas' save/restore stack.
500 canvas->save(SkCanvas::kClip_SaveFlag);
501 canvas->clipRect(bounds);
502 canvas->drawPoints(SkCanvas::kPoints_PointMode, count, verts, paint);
503 canvas->restore();
504 } else {
505 SkPoint pts[2] = { point1, point2 };
506 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
507 }
508 }
509
setrect_for_underline(SkRect * r,GraphicsContext * context,const IntPoint & point,int yOffset,int width)510 static void setrect_for_underline(SkRect* r, GraphicsContext* context, const IntPoint& point, int yOffset, int width)
511 {
512 float lineThickness = context->strokeThickness();
513 // if (lineThickness < 1) // do we really need/want this?
514 // lineThickness = 1;
515
516 yOffset += 1; // we add 1 to have underlines appear below the text
517
518 r->fLeft = SkIntToScalar(point.x());
519 r->fTop = SkIntToScalar(point.y() + yOffset);
520 r->fRight = r->fLeft + SkIntToScalar(width);
521 r->fBottom = r->fTop + SkFloatToScalar(lineThickness);
522 }
523
drawLineForText(IntPoint const & pt,int width,bool)524 void GraphicsContext::drawLineForText(IntPoint const& pt, int width, bool)
525 {
526 if (paintingDisabled())
527 return;
528
529 SkRect r;
530 setrect_for_underline(&r, this, pt, 0, width);
531
532 SkPaint paint;
533 paint.setAntiAlias(true);
534 paint.setColor(this->strokeColor().rgb());
535
536 GC2Canvas(this)->drawRect(r, paint);
537 }
538
drawLineForMisspellingOrBadGrammar(const IntPoint & pt,int width,bool grammar)539 void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& pt, int width, bool grammar)
540 {
541 if (paintingDisabled())
542 return;
543
544 SkRect r;
545 setrect_for_underline(&r, this, pt, 0, width);
546
547 SkPaint paint;
548 paint.setAntiAlias(true);
549 paint.setColor(SK_ColorRED); // is this specified somewhere?
550
551 GC2Canvas(this)->drawRect(r, paint);
552 }
553
554 // This method is only used to draw the little circles used in lists.
drawEllipse(const IntRect & rect)555 void GraphicsContext::drawEllipse(const IntRect& rect)
556 {
557 if (paintingDisabled())
558 return;
559
560 SkPaint paint;
561 SkRect oval(rect);
562
563 if (fillColor().rgb() & 0xFF000000) {
564 m_data->setup_paint_fill(&paint);
565 GC2Canvas(this)->drawOval(oval, paint);
566 }
567 if (strokeStyle() != NoStroke) {
568 paint.reset();
569 m_data->setup_paint_stroke(&paint, &oval);
570 GC2Canvas(this)->drawOval(oval, paint);
571 }
572 }
573
fast_mod(int value,int max)574 static inline int fast_mod(int value, int max)
575 {
576 int sign = SkExtractSign(value);
577
578 value = SkApplySign(value, sign);
579 if (value >= max) {
580 value %= max;
581 }
582 return SkApplySign(value, sign);
583 }
584
strokeArc(const IntRect & r,int startAngle,int angleSpan)585 void GraphicsContext::strokeArc(const IntRect& r, int startAngle, int angleSpan)
586 {
587 if (paintingDisabled())
588 return;
589
590 SkPath path;
591 SkPaint paint;
592 SkRect oval(r);
593
594 if (strokeStyle() == NoStroke) {
595 m_data->setup_paint_fill(&paint); // we want the fill color
596 paint.setStyle(SkPaint::kStroke_Style);
597 paint.setStrokeWidth(SkFloatToScalar(this->strokeThickness()));
598 }
599 else {
600 m_data->setup_paint_stroke(&paint, NULL);
601 }
602
603 // we do this before converting to scalar, so we don't overflow SkFixed
604 startAngle = fast_mod(startAngle, 360);
605 angleSpan = fast_mod(angleSpan, 360);
606
607 path.addArc(oval, SkIntToScalar(-startAngle), SkIntToScalar(-angleSpan));
608 GC2Canvas(this)->drawPath(path, paint);
609 }
610
drawConvexPolygon(size_t numPoints,const FloatPoint * points,bool shouldAntialias)611 void GraphicsContext::drawConvexPolygon(size_t numPoints, const FloatPoint* points, bool shouldAntialias)
612 {
613 if (paintingDisabled())
614 return;
615
616 if (numPoints <= 1)
617 return;
618
619 SkPaint paint;
620 SkPath path;
621
622 path.incReserve(numPoints);
623 path.moveTo(SkFloatToScalar(points[0].x()), SkFloatToScalar(points[0].y()));
624 for (size_t i = 1; i < numPoints; i++)
625 path.lineTo(SkFloatToScalar(points[i].x()), SkFloatToScalar(points[i].y()));
626
627 if (GC2Canvas(this)->quickReject(path, shouldAntialias ?
628 SkCanvas::kAA_EdgeType : SkCanvas::kBW_EdgeType)) {
629 return;
630 }
631
632 if (fillColor().rgb() & 0xFF000000) {
633 m_data->setup_paint_fill(&paint);
634 paint.setAntiAlias(shouldAntialias);
635 GC2Canvas(this)->drawPath(path, paint);
636 }
637
638 if (strokeStyle() != NoStroke) {
639 paint.reset();
640 m_data->setup_paint_stroke(&paint, NULL);
641 paint.setAntiAlias(shouldAntialias);
642 GC2Canvas(this)->drawPath(path, paint);
643 }
644 }
645
fillRoundedRect(const IntRect & rect,const IntSize & topLeft,const IntSize & topRight,const IntSize & bottomLeft,const IntSize & bottomRight,const Color & color,ColorSpace)646 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
647 const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace)
648 {
649 if (paintingDisabled())
650 return;
651
652 SkPaint paint;
653 SkPath path;
654 SkScalar radii[8];
655
656 radii[0] = SkIntToScalar(topLeft.width());
657 radii[1] = SkIntToScalar(topLeft.height());
658 radii[2] = SkIntToScalar(topRight.width());
659 radii[3] = SkIntToScalar(topRight.height());
660 radii[4] = SkIntToScalar(bottomRight.width());
661 radii[5] = SkIntToScalar(bottomRight.height());
662 radii[6] = SkIntToScalar(bottomLeft.width());
663 radii[7] = SkIntToScalar(bottomLeft.height());
664 path.addRoundRect(rect, radii);
665
666 m_data->setup_paint_fill(&paint);
667 GC2Canvas(this)->drawPath(path, paint);
668 }
669
fillRect(const FloatRect & rect)670 void GraphicsContext::fillRect(const FloatRect& rect)
671 {
672 SkPaint paint;
673
674 m_data->setup_paint_fill(&paint);
675
676 extactShader(&paint,
677 m_common->state.fillPattern.get(),
678 m_common->state.fillGradient.get());
679
680 GC2Canvas(this)->drawRect(rect, paint);
681 }
682
fillRect(const FloatRect & rect,const Color & color,ColorSpace)683 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace)
684 {
685 if (paintingDisabled())
686 return;
687
688 if (color.rgb() & 0xFF000000) {
689 SkPaint paint;
690
691 m_data->setup_paint_common(&paint);
692 paint.setColor(color.rgb()); // punch in the specified color
693 paint.setShader(NULL); // in case we had one set
694
695 /* Sometimes we record and draw portions of the page, using clips
696 for each portion. The problem with this is that webkit, sometimes,
697 sees that we're only recording a portion, and they adjust some of
698 their rectangle coordinates accordingly (e.g.
699 RenderBoxModelObject::paintFillLayerExtended() which calls
700 rect.intersect(paintInfo.rect) and then draws the bg with that
701 rect. The result is that we end up drawing rects that are meant to
702 seam together (one for each portion), but if the rects have
703 fractional coordinates (e.g. we are zoomed by a fractional amount)
704 we will double-draw those edges, resulting in visual cracks or
705 artifacts.
706
707 The fix seems to be to just turn off antialasing for rects (this
708 entry-point in GraphicsContext seems to have been sufficient,
709 though perhaps we'll find we need to do this as well in fillRect(r)
710 as well.) Currently setup_paint_common() enables antialiasing.
711
712 Since we never show the page rotated at a funny angle, disabling
713 antialiasing seems to have no real down-side, and it does fix the
714 bug when we're zoomed (and drawing portions that need to seam).
715 */
716 paint.setAntiAlias(false);
717
718 GC2Canvas(this)->drawRect(rect, paint);
719 }
720 }
721
clip(const FloatRect & rect)722 void GraphicsContext::clip(const FloatRect& rect)
723 {
724 if (paintingDisabled())
725 return;
726
727 GC2Canvas(this)->clipRect(rect);
728 }
729
clip(const Path & path)730 void GraphicsContext::clip(const Path& path)
731 {
732 if (paintingDisabled())
733 return;
734
735 // path.platformPath()->dump(false, "clip path");
736
737 GC2Canvas(this)->clipPath(*path.platformPath());
738 }
739
addInnerRoundedRectClip(const IntRect & rect,int thickness)740 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
741 {
742 if (paintingDisabled())
743 return;
744
745 //printf("-------- addInnerRoundedRectClip: [%d %d %d %d] thickness=%d\n", rect.x(), rect.y(), rect.width(), rect.height(), thickness);
746
747 SkPath path;
748 SkRect r(rect);
749
750 path.addOval(r, SkPath::kCW_Direction);
751 // only perform the inset if we won't invert r
752 if (2*thickness < rect.width() && 2*thickness < rect.height())
753 {
754 r.inset(SkIntToScalar(thickness) ,SkIntToScalar(thickness));
755 path.addOval(r, SkPath::kCCW_Direction);
756 }
757 GC2Canvas(this)->clipPath(path);
758 }
759
canvasClip(const Path & path)760 void GraphicsContext::canvasClip(const Path& path)
761 {
762 clip(path);
763 }
764
clipOut(const IntRect & r)765 void GraphicsContext::clipOut(const IntRect& r)
766 {
767 if (paintingDisabled())
768 return;
769
770 GC2Canvas(this)->clipRect(r, SkRegion::kDifference_Op);
771 }
772
clipOutEllipseInRect(const IntRect & r)773 void GraphicsContext::clipOutEllipseInRect(const IntRect& r)
774 {
775 if (paintingDisabled())
776 return;
777
778 SkPath path;
779
780 path.addOval(r, SkPath::kCCW_Direction);
781 GC2Canvas(this)->clipPath(path, SkRegion::kDifference_Op);
782 }
783
784 #if ENABLE(SVG)
clipPath(WindRule clipRule)785 void GraphicsContext::clipPath(WindRule clipRule)
786 {
787 if (paintingDisabled())
788 return;
789 const SkPath* oldPath = m_data->getPath();
790 SkPath path(*oldPath);
791 path.setFillType(clipRule == RULE_EVENODD ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType);
792 GC2Canvas(this)->clipPath(path);
793 }
794 #endif
795
clipOut(const Path & p)796 void GraphicsContext::clipOut(const Path& p)
797 {
798 if (paintingDisabled())
799 return;
800
801 GC2Canvas(this)->clipPath(*p.platformPath(), SkRegion::kDifference_Op);
802 }
803
clipToImageBuffer(const FloatRect &,const ImageBuffer *)804 void GraphicsContext::clipToImageBuffer(const FloatRect&, const ImageBuffer*) {
805 SkDebugf("xxxxxxxxxxxxxxxxxx clipToImageBuffer not implemented\n");
806 }
807
808 //////////////////////////////////////////////////////////////////////////////////////////////////
809
810 #if SVG_SUPPORT
createRenderingDeviceContext()811 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
812 {
813 return new KRenderingDeviceContextQuartz(platformContext());
814 }
815 #endif
816
817 /* These are the flags we need when we call saveLayer for transparency.
818 Since it does not appear that webkit intends this to also save/restore
819 the matrix or clip, I do not give those flags (for performance)
820 */
821 #define TRANSPARENCY_SAVEFLAGS \
822 (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag | \
823 SkCanvas::kFullColorLayer_SaveFlag)
824
beginTransparencyLayer(float opacity)825 void GraphicsContext::beginTransparencyLayer(float opacity)
826 {
827 if (paintingDisabled())
828 return;
829
830 SkCanvas* canvas = GC2Canvas(this);
831 canvas->saveLayerAlpha(NULL, (int)(opacity * 255), TRANSPARENCY_SAVEFLAGS);
832 }
833
endTransparencyLayer()834 void GraphicsContext::endTransparencyLayer()
835 {
836 if (paintingDisabled())
837 return;
838
839 GC2Canvas(this)->restore();
840 }
841
842 ///////////////////////////////////////////////////////////////////////////
843
setupBitmapPaint(SkPaint * paint)844 void GraphicsContext::setupBitmapPaint(SkPaint* paint) {
845 m_data->setup_paint_bitmap(paint);
846 }
847
setupFillPaint(SkPaint * paint)848 void GraphicsContext::setupFillPaint(SkPaint* paint) {
849 m_data->setup_paint_fill(paint);
850 }
851
setupStrokePaint(SkPaint * paint)852 void GraphicsContext::setupStrokePaint(SkPaint* paint) {
853 m_data->setup_paint_stroke(paint, NULL);
854 }
855
setupShadowPaint(SkPaint * paint,SkPoint * offset)856 bool GraphicsContext::setupShadowPaint(SkPaint* paint, SkPoint* offset) {
857 return m_data->mState->setupShadowPaint(paint, offset);
858 }
859
setPlatformStrokeColor(const Color & c,ColorSpace)860 void GraphicsContext::setPlatformStrokeColor(const Color& c, ColorSpace) {
861 m_data->setStrokeColor(c);
862 }
863
setPlatformStrokeThickness(float f)864 void GraphicsContext::setPlatformStrokeThickness(float f) {
865 m_data->setStrokeThickness(f);
866 }
867
setPlatformFillColor(const Color & c,ColorSpace)868 void GraphicsContext::setPlatformFillColor(const Color& c, ColorSpace) {
869 m_data->setFillColor(c);
870 }
871
setPlatformShadow(const IntSize & size,int blur,const Color & color,ColorSpace)872 void GraphicsContext::setPlatformShadow(const IntSize& size, int blur, const Color& color, ColorSpace)
873 {
874 if (paintingDisabled())
875 return;
876
877 if (blur <= 0) {
878 this->clearPlatformShadow();
879 }
880
881 SkColor c;
882 if (color.isValid()) {
883 c = color.rgb();
884 } else {
885 c = SkColorSetARGB(0xFF/3, 0, 0, 0); // "std" Apple shadow color
886 }
887 m_data->mState->setShadow(blur, size.width(), size.height(), c);
888 }
889
clearPlatformShadow()890 void GraphicsContext::clearPlatformShadow()
891 {
892 if (paintingDisabled())
893 return;
894
895 m_data->mState->setShadow(0, 0, 0, 0);
896 }
897
898 ///////////////////////////////////////////////////////////////////////////////
899
drawFocusRing(const Vector<IntRect> &,int,int,const Color &)900 void GraphicsContext::drawFocusRing(const Vector<IntRect>&, int, int, const Color&)
901 {
902 // Do nothing, since we draw the focus ring independently.
903 }
904
drawFocusRing(const Vector<Path> &,int,int,const Color &)905 void GraphicsContext::drawFocusRing(const Vector<Path>&, int, int, const Color&)
906 {
907 // Do nothing, since we draw the focus ring independently.
908 }
909
platformContext() const910 PlatformGraphicsContext* GraphicsContext::platformContext() const
911 {
912 ASSERT(!paintingDisabled());
913 return m_data->mPgc;
914 }
915
setMiterLimit(float limit)916 void GraphicsContext::setMiterLimit(float limit)
917 {
918 m_data->mState->mMiterLimit = limit;
919 }
920
setAlpha(float alpha)921 void GraphicsContext::setAlpha(float alpha)
922 {
923 m_data->mState->mAlpha = alpha;
924 }
925
setCompositeOperation(CompositeOperator op)926 void GraphicsContext::setCompositeOperation(CompositeOperator op)
927 {
928 m_data->mState->mMode = WebCoreCompositeToSkiaComposite(op);
929 }
930
clearRect(const FloatRect & rect)931 void GraphicsContext::clearRect(const FloatRect& rect)
932 {
933 if (paintingDisabled())
934 return;
935
936 SkPaint paint;
937
938 m_data->setup_paint_fill(&paint);
939 paint.setXfermodeMode(SkXfermode::kClear_Mode);
940 GC2Canvas(this)->drawRect(rect, paint);
941 }
942
strokeRect(const FloatRect & rect,float lineWidth)943 void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
944 {
945 if (paintingDisabled())
946 return;
947
948 SkPaint paint;
949
950 m_data->setup_paint_stroke(&paint, NULL);
951 paint.setStrokeWidth(SkFloatToScalar(lineWidth));
952 GC2Canvas(this)->drawRect(rect, paint);
953 }
954
setLineCap(LineCap cap)955 void GraphicsContext::setLineCap(LineCap cap)
956 {
957 switch (cap) {
958 case ButtCap:
959 m_data->mState->mLineCap = SkPaint::kButt_Cap;
960 break;
961 case RoundCap:
962 m_data->mState->mLineCap = SkPaint::kRound_Cap;
963 break;
964 case SquareCap:
965 m_data->mState->mLineCap = SkPaint::kSquare_Cap;
966 break;
967 default:
968 SkDEBUGF(("GraphicsContext::setLineCap: unknown LineCap %d\n", cap));
969 break;
970 }
971 }
972
973 #if ENABLE(SVG)
setLineDash(const DashArray & dashes,float dashOffset)974 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
975 {
976 if (paintingDisabled())
977 return;
978
979 size_t dashLength = dashes.size();
980 if (!dashLength)
981 return;
982
983 size_t count = (dashLength % 2) == 0 ? dashLength : dashLength * 2;
984 SkScalar* intervals = new SkScalar[count];
985
986 for (unsigned int i = 0; i < count; i++)
987 intervals[i] = SkFloatToScalar(dashes[i % dashLength]);
988 SkPathEffect **effectPtr = &m_data->mState->mPathEffect;
989 (*effectPtr)->safeUnref();
990 *effectPtr = new SkDashPathEffect(intervals, count, SkFloatToScalar(dashOffset));
991
992 delete[] intervals;
993 }
994 #endif
995
setLineJoin(LineJoin join)996 void GraphicsContext::setLineJoin(LineJoin join)
997 {
998 switch (join) {
999 case MiterJoin:
1000 m_data->mState->mLineJoin = SkPaint::kMiter_Join;
1001 break;
1002 case RoundJoin:
1003 m_data->mState->mLineJoin = SkPaint::kRound_Join;
1004 break;
1005 case BevelJoin:
1006 m_data->mState->mLineJoin = SkPaint::kBevel_Join;
1007 break;
1008 default:
1009 SkDEBUGF(("GraphicsContext::setLineJoin: unknown LineJoin %d\n", join));
1010 break;
1011 }
1012 }
1013
scale(const FloatSize & size)1014 void GraphicsContext::scale(const FloatSize& size)
1015 {
1016 if (paintingDisabled())
1017 return;
1018 GC2Canvas(this)->scale(SkFloatToScalar(size.width()), SkFloatToScalar(size.height()));
1019 }
1020
rotate(float angleInRadians)1021 void GraphicsContext::rotate(float angleInRadians)
1022 {
1023 if (paintingDisabled())
1024 return;
1025 GC2Canvas(this)->rotate(SkFloatToScalar(angleInRadians * (180.0f / 3.14159265f)));
1026 }
1027
translate(float x,float y)1028 void GraphicsContext::translate(float x, float y)
1029 {
1030 if (paintingDisabled())
1031 return;
1032 GC2Canvas(this)->translate(SkFloatToScalar(x), SkFloatToScalar(y));
1033 }
1034
concatCTM(const AffineTransform & affine)1035 void GraphicsContext::concatCTM(const AffineTransform& affine)
1036 {
1037 if (paintingDisabled())
1038 return;
1039 GC2Canvas(this)->concat(affine);
1040 }
1041
1042 /* This is intended to round the rect to device pixels (through the CTM)
1043 and then invert the result back into source space, with the hope that when
1044 it is drawn (through the matrix), it will land in the "right" place (i.e.
1045 on pixel boundaries).
1046
1047 For android, we record this geometry once and then draw it though various
1048 scale factors as the user zooms, without re-recording. Thus this routine
1049 should just leave the original geometry alone.
1050
1051 If we instead draw into bitmap tiles, we should then perform this
1052 transform -> round -> inverse step.
1053 */
roundToDevicePixels(const FloatRect & rect)1054 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
1055 {
1056 return rect;
1057 }
1058
1059 //////////////////////////////////////////////////////////////////////////////////////////////////
1060
setURLForRect(const KURL & link,const IntRect & destRect)1061 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
1062 {
1063 // appears to be PDF specific, so we ignore it
1064 #if 0
1065 if (paintingDisabled())
1066 return;
1067
1068 CFURLRef urlRef = link.createCFURL();
1069 if (urlRef) {
1070 CGContextRef context = platformContext();
1071
1072 // Get the bounding box to handle clipping.
1073 CGRect box = CGContextGetClipBoundingBox(context);
1074
1075 IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
1076 IntRect rect = destRect;
1077 rect.intersect(intBox);
1078
1079 CGPDFContextSetURLForRect(context, urlRef,
1080 CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
1081
1082 CFRelease(urlRef);
1083 }
1084 #endif
1085 }
1086
setPlatformShouldAntialias(bool useAA)1087 void GraphicsContext::setPlatformShouldAntialias(bool useAA)
1088 {
1089 if (paintingDisabled())
1090 return;
1091 m_data->mState->mUseAA = useAA;
1092 }
1093
getCTM() const1094 AffineTransform GraphicsContext::getCTM() const
1095 {
1096 const SkMatrix& m = GC2Canvas(this)->getTotalMatrix();
1097 return AffineTransform(SkScalarToDouble(m.getScaleX()), // a
1098 SkScalarToDouble(m.getSkewY()), // b
1099 SkScalarToDouble(m.getSkewX()), // c
1100 SkScalarToDouble(m.getScaleY()), // d
1101 SkScalarToDouble(m.getTranslateX()), // e
1102 SkScalarToDouble(m.getTranslateY())); // f
1103 }
1104
1105 ///////////////////////////////////////////////////////////////////////////////
1106
beginPath()1107 void GraphicsContext::beginPath()
1108 {
1109 m_data->beginPath();
1110 }
1111
addPath(const Path & p)1112 void GraphicsContext::addPath(const Path& p)
1113 {
1114 m_data->addPath(*p.platformPath());
1115 }
1116
fillPath()1117 void GraphicsContext::fillPath()
1118 {
1119 SkPath* path = m_data->getPath();
1120 if (paintingDisabled() || !path)
1121 return;
1122
1123 switch (this->fillRule()) {
1124 case RULE_NONZERO:
1125 path->setFillType(SkPath::kWinding_FillType);
1126 break;
1127 case RULE_EVENODD:
1128 path->setFillType(SkPath::kEvenOdd_FillType);
1129 break;
1130 }
1131
1132 SkPaint paint;
1133 m_data->setup_paint_fill(&paint);
1134
1135 extactShader(&paint,
1136 m_common->state.fillPattern.get(),
1137 m_common->state.fillGradient.get());
1138
1139 GC2Canvas(this)->drawPath(*path, paint);
1140 }
1141
strokePath()1142 void GraphicsContext::strokePath()
1143 {
1144 const SkPath* path = m_data->getPath();
1145 if (paintingDisabled() || !path)
1146 return;
1147
1148 SkPaint paint;
1149 m_data->setup_paint_stroke(&paint, NULL);
1150
1151 extactShader(&paint,
1152 m_common->state.strokePattern.get(),
1153 m_common->state.strokeGradient.get());
1154
1155 GC2Canvas(this)->drawPath(*path, paint);
1156 }
1157
setImageInterpolationQuality(InterpolationQuality mode)1158 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode)
1159 {
1160 /*
1161 enum InterpolationQuality {
1162 InterpolationDefault,
1163 InterpolationNone,
1164 InterpolationLow,
1165 InterpolationMedium,
1166 InterpolationHigh
1167 };
1168
1169 TODO: record this, so we can know when to use bitmap-filtering when we draw
1170 ... not sure how meaningful this will be given our playback model.
1171
1172 Certainly safe to do nothing for the present.
1173 */
1174 }
1175
1176 } // namespace WebCore
1177
1178 ///////////////////////////////////////////////////////////////////////////////
1179
android_gc2canvas(WebCore::GraphicsContext * gc)1180 SkCanvas* android_gc2canvas(WebCore::GraphicsContext* gc)
1181 {
1182 return gc->platformContext()->mCanvas;
1183 }
1184