2 * Copyright 2006, The Android Open Source Project
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
26 #include "GraphicsContext.h"
28 #include "AffineTransform.h"
30 #include "NotImplemented.h"
33 #include "PlatformGraphicsContext.h"
34 #include "SkBitmapRef.h"
35 #include "SkBlurDrawLooper.h"
36 #include "SkBlurMaskFilter.h"
38 #include "SkColorPriv.h"
39 #include "SkDashPathEffect.h"
41 #include "SkGradientShader.h"
44 #include "SkiaUtils.h"
45 #include "TransformationMatrix.h"
46 #include "android_graphics.h"
50 #define GC2CANVAS(ctx) (ctx)->m_data->getPlatformGfxCtx()->mCanvas
54 static int RoundToInt(float x)
56 return (int)roundf(x);
59 template <typename T> T* deepCopyPtr(const T* src)
61 return src ? new T(*src) : 0;
64 // Set a bitmap shader that mimics dashing by width-on, width-off.
65 // Returns false if it could not succeed (e.g. there was an existing shader)
66 static bool setBitmapDash(SkPaint* paint, int width) {
67 if (width <= 0 || paint->getShader())
70 SkColor c = paint->getColor();
73 bm.setConfig(SkBitmap::kARGB_8888_Config, 2, 1);
78 *bm.getAddr32(0, 0) = SkPreMultiplyARGB(0xFF, SkColorGetR(c),
79 SkColorGetG(c), SkColorGetB(c));
81 *bm.getAddr32(1, 0) = 0;
85 matrix.setScale(SkIntToScalar(width), SK_Scalar1);
87 SkShader* s = SkShader::CreateBitmapShader(bm, SkShader::kRepeat_TileMode,
88 SkShader::kClamp_TileMode);
89 s->setLocalMatrix(matrix);
91 paint->setShader(s)->unref();
97 // alpha: how does this interact with the alpha in Color? multiply them together?
98 // mode: do I always respect this? If so, then
99 // the rgb() & 0xFF000000 check will abort drawing too often
100 // Is Color premultiplied or not? If it is, then I can't blindly pass it to paint.setColor()
106 SkColor color; // alpha>0 means valid shadow
107 ShadowRec(SkScalar b = 0,
110 SkColor c = 0) // by default, alpha=0, so no shadow
111 : blur(b), dx(x), dy(y), color(c)
115 class GraphicsContextPlatformPrivate {
118 SkPathEffect* pathEffect;
121 float strokeThickness;
122 SkPaint::Cap lineCap;
123 SkPaint::Join lineJoin;
124 SkXfermode::Mode mode;
125 int dashRatio; // Ratio of the length of a dash to its width
130 // This is a list of clipping paths which are currently active, in the
131 // order in which they were pushed.
132 WTF::Vector<SkPath> antiAliasClipPaths;
138 , strokeThickness(0) // Same as default in GraphicsContextPrivate.h
139 , lineCap(SkPaint::kDefault_Cap)
140 , lineJoin(SkPaint::kDefault_Join)
141 , mode(SkXfermode::kSrcOver_Mode)
143 , fillColor(SK_ColorBLACK)
144 , strokeColor(SK_ColorBLACK)
149 State(const State& other)
150 : pathEffect(other.pathEffect)
151 , miterLimit(other.miterLimit)
153 , strokeThickness(other.strokeThickness)
154 , lineCap(other.lineCap)
155 , lineJoin(other.lineJoin)
157 , dashRatio(other.dashRatio)
158 , shadow(other.shadow)
159 , fillColor(other.fillColor)
160 , strokeColor(other.strokeColor)
163 SkSafeRef(pathEffect);
168 SkSafeUnref(pathEffect);
171 void setShadow(int radius, int dx, int dy, SkColor c)
173 // Cut the radius in half, to visually match the effect seen in
175 shadow.blur = SkScalarHalf(SkIntToScalar(radius));
176 shadow.dx = SkIntToScalar(dx);
177 shadow.dy = SkIntToScalar(dy);
181 bool setupShadowPaint(GraphicsContext* ctx, SkPaint* paint, SkPoint* offset)
183 paint->setAntiAlias(true);
184 paint->setDither(true);
185 paint->setXfermodeMode(mode);
186 paint->setColor(shadow.color);
187 offset->set(shadow.dx, shadow.dy);
189 // Currently, only GraphicsContexts associated with the
190 // HTMLCanvasElement have shadows ignore transforms set. This
191 // allows us to distinguish between CSS and Canvas shadows which
192 // have different rendering specifications.
193 uint32_t flags = SkBlurMaskFilter::kHighQuality_BlurFlag;
194 if (ctx->shadowsIgnoreTransforms()) {
195 offset->fY = -offset->fY;
196 flags |= SkBlurMaskFilter::kIgnoreTransform_BlurFlag;
199 if (shadow.blur > 0) {
200 paint->setMaskFilter(SkBlurMaskFilter::Create(shadow.blur,
201 SkBlurMaskFilter::kNormal_BlurStyle))->unref();
203 return SkColorGetA(shadow.color) && (shadow.blur || shadow.dx || shadow.dy);
206 SkColor applyAlpha(SkColor c) const
208 int s = RoundToInt(alpha * 256);
214 int a = SkAlphaMul(SkColorGetA(c), s);
215 return (c & 0x00FFFFFF) | (a << 24);
219 GraphicsContextPlatformPrivate(GraphicsContext* gfxCtx, PlatformGraphicsContext* platformGfxCtx)
220 : m_parentGfxCtx(gfxCtx)
221 , m_platformGfxCtx(platformGfxCtx)
222 , m_stateStack(sizeof(State))
224 State* state = static_cast<State*>(m_stateStack.push_back());
229 ~GraphicsContextPlatformPrivate()
231 // We force restores so we don't leak any subobjects owned by our
232 // stack of State records.
233 while (m_stateStack.count() > 0)
236 if (m_platformGfxCtx && m_platformGfxCtx->deleteUs())
237 delete m_platformGfxCtx;
242 State* newState = static_cast<State*>(m_stateStack.push_back());
243 new (newState) State(*m_state);
249 if (!m_state->antiAliasClipPaths.isEmpty())
250 applyAntiAliasedClipPaths(m_state->antiAliasClipPaths);
253 m_stateStack.pop_back();
254 m_state = static_cast<State*>(m_stateStack.back());
257 void setFillColor(const Color& c)
259 m_state->fillColor = c.rgb();
262 void setStrokeColor(const Color& c)
264 m_state->strokeColor = c.rgb();
267 void setStrokeThickness(float f)
269 m_state->strokeThickness = f;
272 void setupPaintCommon(SkPaint* paint) const
274 paint->setAntiAlias(m_state->useAA);
275 paint->setDither(true);
276 paint->setXfermodeMode(m_state->mode);
277 if (SkColorGetA(m_state->shadow.color) > 0) {
279 // Currently, only GraphicsContexts associated with the
280 // HTMLCanvasElement have shadows ignore transforms set. This
281 // allows us to distinguish between CSS and Canvas shadows which
282 // have different rendering specifications.
283 SkScalar dy = m_state->shadow.dy;
284 uint32_t flags = SkBlurDrawLooper::kHighQuality_BlurFlag;
285 if (m_parentGfxCtx->shadowsIgnoreTransforms()) {
287 flags |= SkBlurDrawLooper::kIgnoreTransform_BlurFlag;
288 flags |= SkBlurDrawLooper::kOverrideColor_BlurFlag;
291 SkDrawLooper* looper = new SkBlurDrawLooper(m_state->shadow.blur,
294 m_state->shadow.color,
296 paint->setLooper(looper)->unref();
300 void setupPaintFill(SkPaint* paint) const
302 this->setupPaintCommon(paint);
303 paint->setColor(m_state->applyAlpha(m_state->fillColor));
306 void setupPaintBitmap(SkPaint* paint) const
308 this->setupPaintCommon(paint);
309 // We only want the global alpha for bitmaps,
310 // so just give applyAlpha opaque black
311 paint->setColor(m_state->applyAlpha(0xFF000000));
314 // Sets up the paint for stroking. Returns true if the style is really
315 // just a dash of squares (the size of the paint's stroke-width.
316 bool setupPaintStroke(SkPaint* paint, SkRect* rect, bool isHLine = false)
318 this->setupPaintCommon(paint);
319 paint->setColor(m_state->applyAlpha(m_state->strokeColor));
321 float width = m_state->strokeThickness;
323 // This allows dashing and dotting to work properly for hairline strokes
324 // FIXME: Should we only do this for dashed and dotted strokes?
328 paint->setStyle(SkPaint::kStroke_Style);
329 paint->setStrokeWidth(SkFloatToScalar(width));
330 paint->setStrokeCap(m_state->lineCap);
331 paint->setStrokeJoin(m_state->lineJoin);
332 paint->setStrokeMiter(SkFloatToScalar(m_state->miterLimit));
334 if (rect && (RoundToInt(width) & 1))
335 rect->inset(-SK_ScalarHalf, -SK_ScalarHalf);
337 SkPathEffect* pe = m_state->pathEffect;
339 paint->setPathEffect(pe);
342 switch (m_parentGfxCtx->strokeStyle()) {
348 width = m_state->dashRatio * width;
356 // Return true if we're basically a dotted dash of squares
357 bool justSqrs = RoundToInt(width) == RoundToInt(paint->getStrokeWidth());
359 if (justSqrs || !isHLine || !setBitmapDash(paint, width)) {
361 // this is slow enough that we just skip it for now
362 // see http://b/issue?id=4163023
363 SkScalar intervals[] = { width, width };
364 pe = new SkDashPathEffect(intervals, 2, 0);
365 paint->setPathEffect(pe)->unref();
373 void clipPathAntiAliased(const SkPath& clipPath)
375 // If we are currently tracking any anti-alias clip paths, then we already
376 // have a layer in place and don't need to add another.
377 bool haveLayerOutstanding = m_state->antiAliasClipPaths.size();
379 // See comments in applyAntiAliasedClipPaths about how this works.
380 m_state->antiAliasClipPaths.append(clipPath);
381 if (!haveLayerOutstanding) {
382 SkRect bounds = clipPath.getBounds();
383 if (m_platformGfxCtx && m_platformGfxCtx->mCanvas) {
384 m_platformGfxCtx->mCanvas->saveLayerAlpha(&bounds, 255,
385 static_cast<SkCanvas::SaveFlags>(SkCanvas::kHasAlphaLayer_SaveFlag
386 | SkCanvas::kFullColorLayer_SaveFlag
387 | SkCanvas::kClipToLayer_SaveFlag));
388 m_platformGfxCtx->mCanvas->save();
394 void applyAntiAliasedClipPaths(WTF::Vector<SkPath>& paths)
396 // Anti-aliased clipping:
398 // Refer to PlatformContextSkia.cpp's applyAntiAliasedClipPaths() for more details
400 if (m_platformGfxCtx && m_platformGfxCtx->mCanvas)
401 m_platformGfxCtx->mCanvas->restore();
404 paint.setXfermodeMode(SkXfermode::kClear_Mode);
405 paint.setAntiAlias(true);
406 paint.setStyle(SkPaint::kFill_Style);
408 if (m_platformGfxCtx && m_platformGfxCtx->mCanvas) {
409 for (size_t i = paths.size() - 1; i < paths.size(); --i) {
410 paths[i].setFillType(SkPath::kInverseWinding_FillType);
411 m_platformGfxCtx->mCanvas->drawPath(paths[i], paint);
413 m_platformGfxCtx->mCanvas->restore();
418 PlatformGraphicsContext* getPlatformGfxCtx()
420 return m_platformGfxCtx;
429 GraphicsContext* m_parentGfxCtx; // Back-ptr to our parent
430 PlatformGraphicsContext* m_platformGfxCtx;
431 SkDeque m_stateStack;
433 State& operator=(const State&);
436 static SkShader::TileMode SpreadMethod2TileMode(GradientSpreadMethod sm)
438 SkShader::TileMode mode = SkShader::kClamp_TileMode;
441 case SpreadMethodPad:
442 mode = SkShader::kClamp_TileMode;
444 case SpreadMethodReflect:
445 mode = SkShader::kMirror_TileMode;
447 case SpreadMethodRepeat:
448 mode = SkShader::kRepeat_TileMode;
454 static void extactShader(SkPaint* paint, Pattern* pat, Gradient* grad)
457 // platformPattern() returns a cached obj
458 paint->setShader(pat->platformPattern(AffineTransform()));
460 // grad->getShader() returns a cached obj
461 GradientSpreadMethod sm = grad->spreadMethod();
462 paint->setShader(grad->getShader(SpreadMethod2TileMode(sm)));
466 ////////////////////////////////////////////////////////////////////////////////////////////////
468 GraphicsContext* GraphicsContext::createOffscreenContext(int width, int height)
470 PlatformGraphicsContext* pgc = new PlatformGraphicsContext();
474 bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
475 bitmap.allocPixels();
476 bitmap.eraseColor(0);
477 pgc->mCanvas->setBitmapDevice(bitmap);
479 GraphicsContext* ctx = new GraphicsContext(pgc);
483 ////////////////////////////////////////////////////////////////////////////////////////////////
485 void GraphicsContext::platformInit(PlatformGraphicsContext* gc)
487 m_data = new GraphicsContextPlatformPrivate(this, gc);
488 setPaintingDisabled(!gc || !gc->mCanvas);
491 void GraphicsContext::platformDestroy()
496 void GraphicsContext::savePlatformState()
498 // Save our private State
500 // Save our native canvas
501 GC2CANVAS(this)->save();
504 void GraphicsContext::restorePlatformState()
506 // Restore our private State
508 // Restore our native canvas
509 GC2CANVAS(this)->restore();
512 bool GraphicsContext::willFill() const
514 return m_data->getState()->fillColor;
517 bool GraphicsContext::willStroke() const
519 return m_data->getState()->strokeColor;
522 // Draws a filled rectangle with a stroked border.
523 void GraphicsContext::drawRect(const IntRect& rect)
525 if (paintingDisabled())
531 if (fillColor().alpha()) {
532 m_data->setupPaintFill(&paint);
533 GC2CANVAS(this)->drawRect(r, paint);
536 // According to GraphicsContext.h, stroking inside drawRect always means
537 // a stroke of 1 inside the rect.
538 if (strokeStyle() != NoStroke && strokeColor().alpha()) {
540 m_data->setupPaintStroke(&paint, &r);
541 paint.setPathEffect(0); // No dashing please
542 paint.setStrokeWidth(SK_Scalar1); // Always just 1.0 width
543 r.inset(SK_ScalarHalf, SK_ScalarHalf); // Ensure we're "inside"
544 GC2CANVAS(this)->drawRect(r, paint);
548 // This is only used to draw borders.
549 void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
551 if (paintingDisabled())
554 StrokeStyle style = strokeStyle();
555 if (style == NoStroke)
559 SkCanvas* canvas = GC2CANVAS(this);
560 const int idx = SkAbs32(point2.x() - point1.x());
561 const int idy = SkAbs32(point2.y() - point1.y());
563 // Special-case horizontal and vertical lines that are really just dots
564 if (m_data->setupPaintStroke(&paint, 0, !idy) && (!idx || !idy)) {
565 const SkScalar diameter = paint.getStrokeWidth();
566 const SkScalar radius = SkScalarHalf(diameter);
567 SkScalar x = SkIntToScalar(SkMin32(point1.x(), point2.x()));
568 SkScalar y = SkIntToScalar(SkMin32(point1.y(), point2.y()));
573 if (!idy) { // Horizontal
574 bounds.set(x, y - radius, x + SkIntToScalar(idx), y + radius);
580 bounds.set(x - radius, y, x + radius, y + SkIntToScalar(idy));
587 // The actual count is the number of ONs we hit alternating
588 // ON(diameter), OFF(diameter), ...
590 SkScalar width = SkScalarDiv(SkIntToScalar(count), diameter);
591 // Now compute the number of cells (ON and OFF)
592 count = SkScalarRound(width);
593 // Now compute the number of ONs
594 count = (count + 1) >> 1;
597 SkAutoMalloc storage(count * sizeof(SkPoint));
598 SkPoint* verts = (SkPoint*)storage.get();
599 // Now build the array of vertices to past to drawPoints
600 for (int i = 0; i < count; i++) {
606 paint.setStyle(SkPaint::kFill_Style);
607 paint.setPathEffect(0);
609 // Clipping to bounds is not required for correctness, but it does
610 // allow us to reject the entire array of points if we are completely
611 // offscreen. This is common in a webpage for android, where most of
612 // the content is clipped out. If drawPoints took an (optional) bounds
613 // parameter, that might even be better, as we would *just* use it for
614 // culling, and not both wacking the canvas' save/restore stack.
615 canvas->save(SkCanvas::kClip_SaveFlag);
616 canvas->clipRect(bounds);
617 canvas->drawPoints(SkCanvas::kPoints_PointMode, count, verts, paint);
620 SkPoint pts[2] = { point1, point2 };
621 canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint);
625 static void setrectForUnderline(SkRect* r, GraphicsContext* context, const FloatPoint& point, int yOffset, float width)
627 float lineThickness = context->strokeThickness();
629 if (lineThickness < 1) // Do we really need/want this?
632 r->fLeft = point.x();
633 r->fTop = point.y() + yOffset;
634 r->fRight = r->fLeft + width;
635 r->fBottom = r->fTop + lineThickness;
638 void GraphicsContext::drawLineForText(const FloatPoint& pt, float width, bool)
640 if (paintingDisabled())
644 setrectForUnderline(&r, this, pt, 0, width);
647 paint.setAntiAlias(true);
648 paint.setColor(this->strokeColor().rgb());
650 GC2CANVAS(this)->drawRect(r, paint);
653 // TODO: Should we draw different based on TextCheckingLineStyle?
654 void GraphicsContext::drawLineForTextChecking(const FloatPoint& pt, float width, TextCheckingLineStyle)
656 if (paintingDisabled())
660 setrectForUnderline(&r, this, pt, 0, width);
663 paint.setAntiAlias(true);
664 paint.setColor(SK_ColorRED); // Is this specified somewhere?
666 GC2CANVAS(this)->drawRect(r, paint);
669 // This method is only used to draw the little circles used in lists.
670 void GraphicsContext::drawEllipse(const IntRect& rect)
672 if (paintingDisabled())
678 if (fillColor().rgb() & 0xFF000000) {
679 m_data->setupPaintFill(&paint);
680 GC2CANVAS(this)->drawOval(oval, paint);
682 if (strokeStyle() != NoStroke) {
684 m_data->setupPaintStroke(&paint, &oval);
685 GC2CANVAS(this)->drawOval(oval, paint);
689 static inline int fastMod(int value, int max)
691 int sign = SkExtractSign(value);
693 value = SkApplySign(value, sign);
696 return SkApplySign(value, sign);
699 void GraphicsContext::strokeArc(const IntRect& r, int startAngle, int angleSpan)
701 if (paintingDisabled())
708 if (strokeStyle() == NoStroke) {
709 m_data->setupPaintFill(&paint); // We want the fill color
710 paint.setStyle(SkPaint::kStroke_Style);
711 paint.setStrokeWidth(SkFloatToScalar(this->strokeThickness()));
713 m_data->setupPaintStroke(&paint, 0);
715 // We do this before converting to scalar, so we don't overflow SkFixed
716 startAngle = fastMod(startAngle, 360);
717 angleSpan = fastMod(angleSpan, 360);
719 path.addArc(oval, SkIntToScalar(-startAngle), SkIntToScalar(-angleSpan));
720 GC2CANVAS(this)->drawPath(path, paint);
723 void GraphicsContext::drawConvexPolygon(size_t numPoints, const FloatPoint* points, bool shouldAntialias)
725 if (paintingDisabled())
734 path.incReserve(numPoints);
735 path.moveTo(SkFloatToScalar(points[0].x()), SkFloatToScalar(points[0].y()));
736 for (size_t i = 1; i < numPoints; i++)
737 path.lineTo(SkFloatToScalar(points[i].x()), SkFloatToScalar(points[i].y()));
739 if (GC2CANVAS(this)->quickReject(path, shouldAntialias ?
740 SkCanvas::kAA_EdgeType : SkCanvas::kBW_EdgeType)) {
744 if (fillColor().rgb() & 0xFF000000) {
745 m_data->setupPaintFill(&paint);
746 paint.setAntiAlias(shouldAntialias);
747 GC2CANVAS(this)->drawPath(path, paint);
750 if (strokeStyle() != NoStroke) {
752 m_data->setupPaintStroke(&paint, 0);
753 paint.setAntiAlias(shouldAntialias);
754 GC2CANVAS(this)->drawPath(path, paint);
758 void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight,
759 const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color, ColorSpace)
761 if (paintingDisabled())
768 radii[0] = SkIntToScalar(topLeft.width());
769 radii[1] = SkIntToScalar(topLeft.height());
770 radii[2] = SkIntToScalar(topRight.width());
771 radii[3] = SkIntToScalar(topRight.height());
772 radii[4] = SkIntToScalar(bottomRight.width());
773 radii[5] = SkIntToScalar(bottomRight.height());
774 radii[6] = SkIntToScalar(bottomLeft.width());
775 radii[7] = SkIntToScalar(bottomLeft.height());
776 path.addRoundRect(rect, radii);
778 m_data->setupPaintFill(&paint);
779 paint.setColor(color.rgb());
780 GC2CANVAS(this)->drawPath(path, paint);
783 void GraphicsContext::fillRect(const FloatRect& rect)
788 m_data->setupPaintFill(&paint);
791 m_state.fillPattern.get(),
792 m_state.fillGradient.get());
794 GC2CANVAS(this)->drawRect(rect, paint);
798 void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace)
800 if (paintingDisabled())
803 if (color.rgb() & 0xFF000000) {
807 m_data->setupPaintCommon(&paint);
808 paint.setColor(color.rgb()); // Punch in the specified color
809 paint.setShader(0); // In case we had one set
811 // Sometimes we record and draw portions of the page, using clips
812 // for each portion. The problem with this is that webkit, sometimes,
813 // sees that we're only recording a portion, and they adjust some of
814 // their rectangle coordinates accordingly (e.g.
815 // RenderBoxModelObject::paintFillLayerExtended() which calls
816 // rect.intersect(paintInfo.rect) and then draws the bg with that
817 // rect. The result is that we end up drawing rects that are meant to
818 // seam together (one for each portion), but if the rects have
819 // fractional coordinates (e.g. we are zoomed by a fractional amount)
820 // we will double-draw those edges, resulting in visual cracks or
823 // The fix seems to be to just turn off antialasing for rects (this
824 // entry-point in GraphicsContext seems to have been sufficient,
825 // though perhaps we'll find we need to do this as well in fillRect(r)
826 // as well.) Currently setupPaintCommon() enables antialiasing.
828 // Since we never show the page rotated at a funny angle, disabling
829 // antialiasing seems to have no real down-side, and it does fix the
830 // bug when we're zoomed (and drawing portions that need to seam).
831 paint.setAntiAlias(false);
833 GC2CANVAS(this)->drawRect(rect, paint);
838 void GraphicsContext::clip(const FloatRect& rect)
840 if (paintingDisabled())
843 GC2CANVAS(this)->clipRect(rect);
846 void GraphicsContext::clip(const Path& path)
848 if (paintingDisabled())
851 m_data->clipPathAntiAliased(*path.platformPath());
854 void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
856 if (paintingDisabled())
862 path.addOval(r, SkPath::kCW_Direction);
863 // Only perform the inset if we won't invert r
864 if (2 * thickness < rect.width() && 2 * thickness < rect.height()) {
865 // Adding one to the thickness doesn't make the border too thick as
866 // it's painted over afterwards. But without this adjustment the
867 // border appears a little anemic after anti-aliasing.
868 r.inset(SkIntToScalar(thickness + 1), SkIntToScalar(thickness + 1));
869 path.addOval(r, SkPath::kCCW_Direction);
871 m_data->clipPathAntiAliased(path);
874 void GraphicsContext::canvasClip(const Path& path)
879 void GraphicsContext::clipOut(const IntRect& r)
881 if (paintingDisabled())
884 GC2CANVAS(this)->clipRect(r, SkRegion::kDifference_Op);
888 void GraphicsContext::clipPath(const Path& pathToClip, WindRule clipRule)
890 if (paintingDisabled())
893 SkPath path = *pathToClip.platformPath();
894 path.setFillType(clipRule == RULE_EVENODD ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType);
895 GC2CANVAS(this)->clipPath(path);
899 void GraphicsContext::clipOut(const Path& p)
901 if (paintingDisabled())
904 GC2CANVAS(this)->clipPath(*p.platformPath(), SkRegion::kDifference_Op);
907 //////////////////////////////////////////////////////////////////////////////////////////////////
910 KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext()
912 return new KRenderingDeviceContextQuartz(platformContext());
916 // These are the flags we need when we call saveLayer for transparency.
917 // Since it does not appear that webkit intends this to also save/restore
918 // the matrix or clip, I do not give those flags (for performance)
919 #define TRANSPARENCY_SAVEFLAGS \
920 (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag | \
921 SkCanvas::kFullColorLayer_SaveFlag)
923 void GraphicsContext::beginTransparencyLayer(float opacity)
925 if (paintingDisabled())
928 SkCanvas* canvas = GC2CANVAS(this);
929 canvas->saveLayerAlpha(0, (int)(opacity * 255), TRANSPARENCY_SAVEFLAGS);
932 void GraphicsContext::endTransparencyLayer()
934 if (paintingDisabled())
937 GC2CANVAS(this)->restore();
940 ///////////////////////////////////////////////////////////////////////////
942 void GraphicsContext::setupBitmapPaint(SkPaint* paint)
944 m_data->setupPaintBitmap(paint);
947 void GraphicsContext::setupFillPaint(SkPaint* paint)
949 m_data->setupPaintFill(paint);
952 void GraphicsContext::setupStrokePaint(SkPaint* paint)
954 m_data->setupPaintStroke(paint, 0);
957 bool GraphicsContext::setupShadowPaint(SkPaint* paint, SkPoint* offset)
959 return m_data->getState()->setupShadowPaint(this, paint, offset);
962 void GraphicsContext::setPlatformStrokeColor(const Color& c, ColorSpace)
964 m_data->setStrokeColor(c);
967 void GraphicsContext::setPlatformStrokeThickness(float f)
969 m_data->setStrokeThickness(f);
972 void GraphicsContext::setPlatformFillColor(const Color& c, ColorSpace)
974 m_data->setFillColor(c);
977 void GraphicsContext::setPlatformShadow(const FloatSize& size, float blur, const Color& color, ColorSpace)
979 if (paintingDisabled())
983 this->clearPlatformShadow();
989 c = SkColorSetARGB(0xFF / 3, 0, 0, 0); // "std" Apple shadow color
990 m_data->getState()->setShadow(blur, size.width(), size.height(), c);
993 void GraphicsContext::clearPlatformShadow()
995 if (paintingDisabled())
998 m_data->getState()->setShadow(0, 0, 0, 0);
1001 ///////////////////////////////////////////////////////////////////////////////
1003 void GraphicsContext::drawFocusRing(const Vector<IntRect>&, int, int, const Color&)
1005 // Do nothing, since we draw the focus ring independently.
1008 void GraphicsContext::drawFocusRing(const Path&, int, int, const Color&)
1010 // Do nothing, since we draw the focus ring independently.
1013 PlatformGraphicsContext* GraphicsContext::platformContext() const
1015 ASSERT(!paintingDisabled());
1016 return m_data->getPlatformGfxCtx();
1019 void GraphicsContext::setMiterLimit(float limit)
1021 m_data->getState()->miterLimit = limit;
1024 void GraphicsContext::setAlpha(float alpha)
1026 m_data->getState()->alpha = alpha;
1029 void GraphicsContext::setPlatformCompositeOperation(CompositeOperator op)
1031 m_data->getState()->mode = WebCoreCompositeToSkiaComposite(op);
1034 void GraphicsContext::clearRect(const FloatRect& rect)
1036 if (paintingDisabled())
1041 m_data->setupPaintFill(&paint);
1042 paint.setXfermodeMode(SkXfermode::kClear_Mode);
1043 GC2CANVAS(this)->drawRect(rect, paint);
1046 void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
1048 if (paintingDisabled())
1053 m_data->setupPaintStroke(&paint, 0);
1054 paint.setStrokeWidth(SkFloatToScalar(lineWidth));
1055 GC2CANVAS(this)->drawRect(rect, paint);
1058 void GraphicsContext::setLineCap(LineCap cap)
1062 m_data->getState()->lineCap = SkPaint::kButt_Cap;
1065 m_data->getState()->lineCap = SkPaint::kRound_Cap;
1068 m_data->getState()->lineCap = SkPaint::kSquare_Cap;
1071 SkDEBUGF(("GraphicsContext::setLineCap: unknown LineCap %d\n", cap));
1077 void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
1079 if (paintingDisabled())
1082 size_t dashLength = dashes.size();
1086 size_t count = !(dashLength % 2) ? dashLength : dashLength * 2;
1087 SkScalar* intervals = new SkScalar[count];
1089 for (unsigned int i = 0; i < count; i++)
1090 intervals[i] = SkFloatToScalar(dashes[i % dashLength]);
1091 SkPathEffect **effectPtr = &m_data->getState()->pathEffect;
1092 SkSafeUnref(*effectPtr);
1093 *effectPtr = new SkDashPathEffect(intervals, count, SkFloatToScalar(dashOffset));
1099 void GraphicsContext::setLineJoin(LineJoin join)
1103 m_data->getState()->lineJoin = SkPaint::kMiter_Join;
1106 m_data->getState()->lineJoin = SkPaint::kRound_Join;
1109 m_data->getState()->lineJoin = SkPaint::kBevel_Join;
1112 SkDEBUGF(("GraphicsContext::setLineJoin: unknown LineJoin %d\n", join));
1117 void GraphicsContext::scale(const FloatSize& size)
1119 if (paintingDisabled())
1121 GC2CANVAS(this)->scale(SkFloatToScalar(size.width()), SkFloatToScalar(size.height()));
1124 void GraphicsContext::rotate(float angleInRadians)
1126 if (paintingDisabled())
1128 GC2CANVAS(this)->rotate(SkFloatToScalar(angleInRadians * (180.0f / 3.14159265f)));
1131 void GraphicsContext::translate(float x, float y)
1133 if (paintingDisabled())
1135 GC2CANVAS(this)->translate(SkFloatToScalar(x), SkFloatToScalar(y));
1138 void GraphicsContext::concatCTM(const AffineTransform& affine)
1140 if (paintingDisabled())
1142 GC2CANVAS(this)->concat(affine);
1145 // This is intended to round the rect to device pixels (through the CTM)
1146 // and then invert the result back into source space, with the hope that when
1147 // it is drawn (through the matrix), it will land in the "right" place (i.e.
1148 // on pixel boundaries).
1150 // For android, we record this geometry once and then draw it though various
1151 // scale factors as the user zooms, without re-recording. Thus this routine
1152 // should just leave the original geometry alone.
1154 // If we instead draw into bitmap tiles, we should then perform this
1155 // transform -> round -> inverse step.
1157 FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect, RoundingMode)
1162 //////////////////////////////////////////////////////////////////////////////////////////////////
1164 void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
1166 // Appears to be PDF specific, so we ignore it
1168 if (paintingDisabled())
1171 CFURLRef urlRef = link.createCFURL();
1173 CGContextRef context = platformContext();
1175 // Get the bounding box to handle clipping.
1176 CGRect box = CGContextGetClipBoundingBox(context);
1178 IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height);
1179 IntRect rect = destRect;
1180 rect.intersect(intBox);
1182 CGPDFContextSetURLForRect(context, urlRef,
1183 CGRectApplyAffineTransform(rect, CGContextGetCTM(context)));
1190 void GraphicsContext::setPlatformShouldAntialias(bool useAA)
1192 if (paintingDisabled())
1194 m_data->getState()->useAA = useAA;
1197 AffineTransform GraphicsContext::getCTM() const
1199 const SkMatrix& m = GC2CANVAS(this)->getTotalMatrix();
1200 return AffineTransform(SkScalarToDouble(m.getScaleX()), // a
1201 SkScalarToDouble(m.getSkewY()), // b
1202 SkScalarToDouble(m.getSkewX()), // c
1203 SkScalarToDouble(m.getScaleY()), // d
1204 SkScalarToDouble(m.getTranslateX()), // e
1205 SkScalarToDouble(m.getTranslateY())); // f
1208 void GraphicsContext::setCTM(const AffineTransform& transform)
1210 if (paintingDisabled())
1213 GC2CANVAS(this)->setMatrix(transform);
1216 ///////////////////////////////////////////////////////////////////////////////
1218 void GraphicsContext::fillPath(const Path& pathToFill)
1220 SkPath* path = pathToFill.platformPath();
1221 if (paintingDisabled() || !path)
1224 switch (this->fillRule()) {
1226 path->setFillType(SkPath::kWinding_FillType);
1229 path->setFillType(SkPath::kEvenOdd_FillType);
1234 m_data->setupPaintFill(&paint);
1236 extactShader(&paint,
1237 m_state.fillPattern.get(),
1238 m_state.fillGradient.get());
1240 GC2CANVAS(this)->drawPath(*path, paint);
1243 void GraphicsContext::strokePath(const Path& pathToStroke)
1245 const SkPath* path = pathToStroke.platformPath();
1246 if (paintingDisabled() || !path)
1250 m_data->setupPaintStroke(&paint, 0);
1252 extactShader(&paint,
1253 m_state.strokePattern.get(),
1254 m_state.strokeGradient.get());
1256 GC2CANVAS(this)->drawPath(*path, paint);
1259 InterpolationQuality GraphicsContext::imageInterpolationQuality() const
1262 return InterpolationDefault;
1265 void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode)
1268 enum InterpolationQuality {
1269 InterpolationDefault,
1272 InterpolationMedium,
1276 // TODO: record this, so we can know when to use bitmap-filtering when we draw
1277 // ... not sure how meaningful this will be given our playback model.
1279 // Certainly safe to do nothing for the present.
1282 void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint*, bool antialias)
1284 if (paintingDisabled())
1290 // FIXME: IMPLEMENT!
1293 } // namespace WebCore
1295 ///////////////////////////////////////////////////////////////////////////////
1297 SkCanvas* android_gc2canvas(WebCore::GraphicsContext* gc)
1299 return gc->platformContext()->mCanvas;