/* * Copyright 2006, The Android Open Source Project * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "Gradient.h" #include "GraphicsContext.h" #include "GraphicsContextPrivate.h" #include "NotImplemented.h" #include "Path.h" #include "Pattern.h" #include "SkBlurDrawLooper.h" #include "SkBlurMaskFilter.h" #include "SkCanvas.h" #include "SkColorPriv.h" #include "SkDashPathEffect.h" #include "SkDevice.h" #include "SkPaint.h" #include "PlatformGraphicsContext.h" #include "TransformationMatrix.h" #include "android_graphics.h" #include "SkGradientShader.h" #include "SkBitmapRef.h" #include "SkString.h" #include "SkiaUtils.h" using namespace std; #define GC2Canvas(ctx) (ctx)->m_data->mPgc->mCanvas namespace WebCore { static int RoundToInt(float x) { return (int)roundf(x); } template T* deepCopyPtr(const T* src) { return src ? new T(*src) : NULL; } /* TODO / questions mAlpha: how does this interact with the alpha in Color? multiply them together? mMode: do I always respect this? If so, then the rgb() & 0xFF000000 check will abort drawing too often Is Color premultiplied or not? If it is, then I can't blindly pass it to paint.setColor() */ struct ShadowRec { SkScalar mBlur; // >0 means valid shadow SkScalar mDx; SkScalar mDy; SkColor mColor; }; class GraphicsContextPlatformPrivate { public: GraphicsContext* mCG; // back-ptr to our parent PlatformGraphicsContext* mPgc; struct State { SkPath* mPath; SkPathEffect* mPathEffect; float mMiterLimit; float mAlpha; float mStrokeThickness; SkPaint::Cap mLineCap; SkPaint::Join mLineJoin; SkXfermode::Mode mMode; int mDashRatio; //ratio of the length of a dash to its width ShadowRec mShadow; SkColor mFillColor; SkColor mStrokeColor; bool mUseAA; State() { mPath = NULL; // lazily allocated mPathEffect = 0; mMiterLimit = 4; mAlpha = 1; mStrokeThickness = 0.0f; // Same as default in GraphicsContextPrivate.h mLineCap = SkPaint::kDefault_Cap; mLineJoin = SkPaint::kDefault_Join; mMode = SkXfermode::kSrcOver_Mode; mDashRatio = 3; mUseAA = true; mShadow.mBlur = 0; mFillColor = SK_ColorBLACK; mStrokeColor = SK_ColorBLACK; } State(const State& other) { memcpy(this, &other, sizeof(State)); mPath = deepCopyPtr(other.mPath); mPathEffect->safeRef(); } ~State() { delete mPath; mPathEffect->safeUnref(); } void setShadow(int radius, int dx, int dy, SkColor c) { // cut the radius in half, to visually match the effect seen in // safari browser mShadow.mBlur = SkScalarHalf(SkIntToScalar(radius)); mShadow.mDx = SkIntToScalar(dx); mShadow.mDy = SkIntToScalar(dy); mShadow.mColor = c; } bool setupShadowPaint(SkPaint* paint, SkPoint* offset) { if (mShadow.mBlur > 0) { paint->setAntiAlias(true); paint->setDither(true); paint->setXfermodeMode(mMode); paint->setColor(mShadow.mColor); paint->setMaskFilter(SkBlurMaskFilter::Create(mShadow.mBlur, SkBlurMaskFilter::kNormal_BlurStyle))->unref(); offset->set(mShadow.mDx, mShadow.mDy); return true; } return false; } SkColor applyAlpha(SkColor c) const { int s = RoundToInt(mAlpha * 256); if (s >= 256) return c; if (s < 0) return 0; int a = SkAlphaMul(SkColorGetA(c), s); return (c & 0x00FFFFFF) | (a << 24); } }; SkDeque mStateStack; State* mState; GraphicsContextPlatformPrivate(GraphicsContext* cg, PlatformGraphicsContext* pgc) : mCG(cg) , mPgc(pgc), mStateStack(sizeof(State)) { State* state = (State*)mStateStack.push_back(); new (state) State(); mState = state; } ~GraphicsContextPlatformPrivate() { if (mPgc && mPgc->deleteUs()) delete mPgc; // we force restores so we don't leak any subobjects owned by our // stack of State records. while (mStateStack.count() > 0) this->restore(); } void save() { State* newState = (State*)mStateStack.push_back(); new (newState) State(*mState); mState = newState; } void restore() { mState->~State(); mStateStack.pop_back(); mState = (State*)mStateStack.back(); } void setFillColor(const Color& c) { mState->mFillColor = c.rgb(); } void setStrokeColor(const Color& c) { mState->mStrokeColor = c.rgb(); } void setStrokeThickness(float f) { mState->mStrokeThickness = f; } void beginPath() { if (mState->mPath) { mState->mPath->reset(); } } void addPath(const SkPath& other) { if (!mState->mPath) { mState->mPath = new SkPath(other); } else { mState->mPath->addPath(other); } } // may return null SkPath* getPath() const { return mState->mPath; } void setup_paint_common(SkPaint* paint) const { paint->setAntiAlias(mState->mUseAA); paint->setDither(true); paint->setXfermodeMode(mState->mMode); if (mState->mShadow.mBlur > 0) { SkDrawLooper* looper = new SkBlurDrawLooper(mState->mShadow.mBlur, mState->mShadow.mDx, mState->mShadow.mDy, mState->mShadow.mColor); paint->setLooper(looper)->unref(); } /* need to sniff textDrawingMode(), which returns the bit_OR of... const int cTextInvisible = 0; const int cTextFill = 1; const int cTextStroke = 2; const int cTextClip = 4; */ } void setup_paint_fill(SkPaint* paint) const { this->setup_paint_common(paint); paint->setColor(mState->applyAlpha(mState->mFillColor)); } /* sets up the paint for stroking. Returns true if the style is really just a dash of squares (the size of the paint's stroke-width. */ bool setup_paint_stroke(SkPaint* paint, SkRect* rect) { this->setup_paint_common(paint); paint->setColor(mState->applyAlpha(mState->mStrokeColor)); float width = mState->mStrokeThickness; // this allows dashing and dotting to work properly for hairline strokes // FIXME: Should we only do this for dashed and dotted strokes? if (0 == width) { width = 1; } // paint->setColor(mState->applyAlpha(mCG->strokeColor().rgb())); paint->setStyle(SkPaint::kStroke_Style); paint->setStrokeWidth(SkFloatToScalar(width)); paint->setStrokeCap(mState->mLineCap); paint->setStrokeJoin(mState->mLineJoin); paint->setStrokeMiter(SkFloatToScalar(mState->mMiterLimit)); if (rect != NULL && (RoundToInt(width) & 1)) { rect->inset(-SK_ScalarHalf, -SK_ScalarHalf); } SkPathEffect* pe = mState->mPathEffect; if (pe) { paint->setPathEffect(pe); return false; } switch (mCG->strokeStyle()) { case NoStroke: case SolidStroke: width = 0; break; case DashedStroke: width = mState->mDashRatio * width; break; /* no break */ case DottedStroke: break; } if (width > 0) { SkScalar intervals[] = { width, width }; pe = new SkDashPathEffect(intervals, 2, 0); paint->setPathEffect(pe)->unref(); // return true if we're basically a dotted dash of squares return RoundToInt(width) == RoundToInt(paint->getStrokeWidth()); } return false; } private: // not supported yet State& operator=(const State&); }; static SkShader::TileMode SpreadMethod2TileMode(GradientSpreadMethod sm) { SkShader::TileMode mode = SkShader::kClamp_TileMode; switch (sm) { case SpreadMethodPad: mode = SkShader::kClamp_TileMode; break; case SpreadMethodReflect: mode = SkShader::kMirror_TileMode; break; case SpreadMethodRepeat: mode = SkShader::kRepeat_TileMode; break; } return mode; } static void extactShader(SkPaint* paint, ColorSpace cs, Pattern* pat, Gradient* grad) { switch (cs) { case PatternColorSpace: // createPlatformPattern() returns a new inst paint->setShader(pat->createPlatformPattern( TransformationMatrix()))->safeUnref(); break; case GradientColorSpace: { // grad->getShader() returns a cached obj GradientSpreadMethod sm = grad->spreadMethod(); paint->setShader(grad->getShader(SpreadMethod2TileMode(sm))); break; } default: break; } } //////////////////////////////////////////////////////////////////////////////////////////////// GraphicsContext* GraphicsContext::createOffscreenContext(int width, int height) { PlatformGraphicsContext* pgc = new PlatformGraphicsContext(); SkBitmap bitmap; bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height); bitmap.allocPixels(); bitmap.eraseColor(0); pgc->mCanvas->setBitmapDevice(bitmap); GraphicsContext* ctx = new GraphicsContext(pgc); //printf("-------- create offscreen %p\n", ctx); return ctx; } //////////////////////////////////////////////////////////////////////////////////////////////// GraphicsContext::GraphicsContext(PlatformGraphicsContext *gc) : m_common(createGraphicsContextPrivate()) , m_data(new GraphicsContextPlatformPrivate(this, gc)) { setPaintingDisabled(NULL == gc || NULL == gc->mCanvas); } GraphicsContext::~GraphicsContext() { delete m_data; this->destroyGraphicsContextPrivate(m_common); } void GraphicsContext::savePlatformState() { // save our private State m_data->save(); // save our native canvas GC2Canvas(this)->save(); } void GraphicsContext::restorePlatformState() { // restore our native canvas GC2Canvas(this)->restore(); // restore our private State m_data->restore(); } bool GraphicsContext::willFill() const { return m_data->mState->mFillColor != 0; } bool GraphicsContext::willStroke() const { return m_data->mState->mStrokeColor != 0; } const SkPath* GraphicsContext::getCurrPath() const { return m_data->mState->mPath; } // Draws a filled rectangle with a stroked border. void GraphicsContext::drawRect(const IntRect& rect) { if (paintingDisabled()) return; SkPaint paint; SkRect r(rect); if (fillColor().alpha()) { m_data->setup_paint_fill(&paint); GC2Canvas(this)->drawRect(r, paint); } if (strokeStyle() != NoStroke && strokeColor().alpha()) { paint.reset(); m_data->setup_paint_stroke(&paint, &r); GC2Canvas(this)->drawRect(r, paint); } } // This is only used to draw borders. void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2) { if (paintingDisabled()) return; StrokeStyle style = strokeStyle(); if (style == NoStroke) return; SkPaint paint; SkCanvas* canvas = GC2Canvas(this); const int idx = SkAbs32(point2.x() - point1.x()); const int idy = SkAbs32(point2.y() - point1.y()); // special-case horizontal and vertical lines that are really just dots if (m_data->setup_paint_stroke(&paint, NULL) && (0 == idx || 0 == idy)) { const SkScalar diameter = paint.getStrokeWidth(); const SkScalar radius = SkScalarHalf(diameter); SkScalar x = SkIntToScalar(SkMin32(point1.x(), point2.x())); SkScalar y = SkIntToScalar(SkMin32(point1.y(), point2.y())); SkScalar dx, dy; int count; SkRect bounds; if (0 == idy) { // horizontal bounds.set(x, y - radius, x + SkIntToScalar(idx), y + radius); x += radius; dx = diameter * 2; dy = 0; count = idx; } else { // vertical bounds.set(x - radius, y, x + radius, y + SkIntToScalar(idy)); y += radius; dx = 0; dy = diameter * 2; count = idy; } // the actual count is the number of ONs we hit alternating // ON(diameter), OFF(diameter), ... { SkScalar width = SkScalarDiv(SkIntToScalar(count), diameter); // now computer the number of cells (ON and OFF) count = SkScalarRound(width); // now compute the number of ONs count = (count + 1) >> 1; } SkAutoMalloc storage(count * sizeof(SkPoint)); SkPoint* verts = (SkPoint*)storage.get(); // now build the array of vertices to past to drawPoints for (int i = 0; i < count; i++) { verts[i].set(x, y); x += dx; y += dy; } paint.setStyle(SkPaint::kFill_Style); paint.setPathEffect(NULL); // clipping to bounds is not required for correctness, but it does // allow us to reject the entire array of points if we are completely // offscreen. This is common in a webpage for android, where most of // the content is clipped out. If drawPoints took an (optional) bounds // parameter, that might even be better, as we would *just* use it for // culling, and not both wacking the canvas' save/restore stack. canvas->save(SkCanvas::kClip_SaveFlag); canvas->clipRect(bounds); canvas->drawPoints(SkCanvas::kPoints_PointMode, count, verts, paint); canvas->restore(); } else { SkPoint pts[2] = { point1, point2 }; canvas->drawLine(pts[0].fX, pts[0].fY, pts[1].fX, pts[1].fY, paint); } } static void setrect_for_underline(SkRect* r, GraphicsContext* context, const IntPoint& point, int yOffset, int width) { float lineThickness = context->strokeThickness(); // if (lineThickness < 1) // do we really need/want this? // lineThickness = 1; yOffset += 1; // we add 1 to have underlines appear below the text r->fLeft = SkIntToScalar(point.x()); r->fTop = SkIntToScalar(point.y() + yOffset); r->fRight = r->fLeft + SkIntToScalar(width); r->fBottom = r->fTop + SkFloatToScalar(lineThickness); } void GraphicsContext::drawLineForText(IntPoint const& pt, int width, bool) { if (paintingDisabled()) return; SkRect r; setrect_for_underline(&r, this, pt, 0, width); SkPaint paint; paint.setAntiAlias(true); paint.setColor(this->strokeColor().rgb()); GC2Canvas(this)->drawRect(r, paint); } void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& pt, int width, bool grammar) { if (paintingDisabled()) return; SkRect r; setrect_for_underline(&r, this, pt, 0, width); SkPaint paint; paint.setAntiAlias(true); paint.setColor(SK_ColorRED); // is this specified somewhere? GC2Canvas(this)->drawRect(r, paint); } // This method is only used to draw the little circles used in lists. void GraphicsContext::drawEllipse(const IntRect& rect) { if (paintingDisabled()) return; SkPaint paint; SkRect oval(rect); if (fillColor().rgb() & 0xFF000000) { m_data->setup_paint_fill(&paint); GC2Canvas(this)->drawOval(oval, paint); } if (strokeStyle() != NoStroke) { paint.reset(); m_data->setup_paint_stroke(&paint, &oval); GC2Canvas(this)->drawOval(oval, paint); } } static inline int fast_mod(int value, int max) { int sign = SkExtractSign(value); value = SkApplySign(value, sign); if (value >= max) { value %= max; } return SkApplySign(value, sign); } void GraphicsContext::strokeArc(const IntRect& r, int startAngle, int angleSpan) { if (paintingDisabled()) return; SkPath path; SkPaint paint; SkRect oval(r); if (strokeStyle() == NoStroke) { m_data->setup_paint_fill(&paint); // we want the fill color paint.setStyle(SkPaint::kStroke_Style); paint.setStrokeWidth(SkFloatToScalar(this->strokeThickness())); } else { m_data->setup_paint_stroke(&paint, NULL); } // we do this before converting to scalar, so we don't overflow SkFixed startAngle = fast_mod(startAngle, 360); angleSpan = fast_mod(angleSpan, 360); path.addArc(oval, SkIntToScalar(-startAngle), SkIntToScalar(-angleSpan)); GC2Canvas(this)->drawPath(path, paint); } void GraphicsContext::drawConvexPolygon(size_t numPoints, const FloatPoint* points, bool shouldAntialias) { if (paintingDisabled()) return; if (numPoints <= 1) return; SkPaint paint; SkPath path; path.incReserve(numPoints); path.moveTo(SkFloatToScalar(points[0].x()), SkFloatToScalar(points[0].y())); for (size_t i = 1; i < numPoints; i++) path.lineTo(SkFloatToScalar(points[i].x()), SkFloatToScalar(points[i].y())); if (GC2Canvas(this)->quickReject(path, shouldAntialias ? SkCanvas::kAA_EdgeType : SkCanvas::kBW_EdgeType)) { return; } if (fillColor().rgb() & 0xFF000000) { m_data->setup_paint_fill(&paint); paint.setAntiAlias(shouldAntialias); GC2Canvas(this)->drawPath(path, paint); } if (strokeStyle() != NoStroke) { paint.reset(); m_data->setup_paint_stroke(&paint, NULL); paint.setAntiAlias(shouldAntialias); GC2Canvas(this)->drawPath(path, paint); } } void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color) { if (paintingDisabled()) return; SkPaint paint; SkPath path; SkScalar radii[8]; radii[0] = SkIntToScalar(topLeft.width()); radii[1] = SkIntToScalar(topLeft.height()); radii[2] = SkIntToScalar(topRight.width()); radii[3] = SkIntToScalar(topRight.height()); radii[4] = SkIntToScalar(bottomRight.width()); radii[5] = SkIntToScalar(bottomRight.height()); radii[6] = SkIntToScalar(bottomLeft.width()); radii[7] = SkIntToScalar(bottomLeft.height()); path.addRoundRect(rect, radii); m_data->setup_paint_fill(&paint); GC2Canvas(this)->drawPath(path, paint); } void GraphicsContext::fillRect(const FloatRect& rect) { SkPaint paint; m_data->setup_paint_fill(&paint); extactShader(&paint, m_common->state.fillColorSpace, m_common->state.fillPattern.get(), m_common->state.fillGradient.get()); GC2Canvas(this)->drawRect(rect, paint); } void GraphicsContext::fillRect(const FloatRect& rect, const Color& color) { if (paintingDisabled()) return; if (color.rgb() & 0xFF000000) { SkPaint paint; m_data->setup_paint_common(&paint); paint.setColor(color.rgb()); // punch in the specified color paint.setShader(NULL); // in case we had one set GC2Canvas(this)->drawRect(rect, paint); } } void GraphicsContext::clip(const FloatRect& rect) { if (paintingDisabled()) return; GC2Canvas(this)->clipRect(rect); } void GraphicsContext::clip(const Path& path) { if (paintingDisabled()) return; // path.platformPath()->dump(false, "clip path"); GC2Canvas(this)->clipPath(*path.platformPath()); } void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness) { if (paintingDisabled()) return; //printf("-------- addInnerRoundedRectClip: [%d %d %d %d] thickness=%d\n", rect.x(), rect.y(), rect.width(), rect.height(), thickness); SkPath path; SkRect r(rect); path.addOval(r, SkPath::kCW_Direction); // only perform the inset if we won't invert r if (2*thickness < rect.width() && 2*thickness < rect.height()) { r.inset(SkIntToScalar(thickness) ,SkIntToScalar(thickness)); path.addOval(r, SkPath::kCCW_Direction); } GC2Canvas(this)->clipPath(path); } void GraphicsContext::clipOut(const IntRect& r) { if (paintingDisabled()) return; GC2Canvas(this)->clipRect(r, SkRegion::kDifference_Op); } void GraphicsContext::clipOutEllipseInRect(const IntRect& r) { if (paintingDisabled()) return; SkPath path; path.addOval(r, SkPath::kCCW_Direction); GC2Canvas(this)->clipPath(path, SkRegion::kDifference_Op); } #if ENABLE(SVG) void GraphicsContext::clipPath(WindRule clipRule) { if (paintingDisabled()) return; const SkPath* oldPath = m_data->getPath(); SkPath path(*oldPath); path.setFillType(clipRule == RULE_EVENODD ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType); GC2Canvas(this)->clipPath(path); } #endif void GraphicsContext::clipOut(const Path& p) { if (paintingDisabled()) return; GC2Canvas(this)->clipPath(*p.platformPath(), SkRegion::kDifference_Op); } void GraphicsContext::clipToImageBuffer(const FloatRect&, const ImageBuffer*) { SkDebugf("xxxxxxxxxxxxxxxxxx clipToImageBuffer not implemented\n"); } ////////////////////////////////////////////////////////////////////////////////////////////////// #if SVG_SUPPORT KRenderingDeviceContext* GraphicsContext::createRenderingDeviceContext() { return new KRenderingDeviceContextQuartz(platformContext()); } #endif /* These are the flags we need when we call saveLayer for transparency. Since it does not appear that webkit intends this to also save/restore the matrix or clip, I do not give those flags (for performance) */ #define TRANSPARENCY_SAVEFLAGS \ (SkCanvas::SaveFlags)(SkCanvas::kHasAlphaLayer_SaveFlag | \ SkCanvas::kFullColorLayer_SaveFlag) void GraphicsContext::beginTransparencyLayer(float opacity) { if (paintingDisabled()) return; SkCanvas* canvas = GC2Canvas(this); canvas->saveLayerAlpha(NULL, (int)(opacity * 255), TRANSPARENCY_SAVEFLAGS); } void GraphicsContext::endTransparencyLayer() { if (paintingDisabled()) return; GC2Canvas(this)->restore(); } /////////////////////////////////////////////////////////////////////////// void GraphicsContext::setupFillPaint(SkPaint* paint) { m_data->setup_paint_fill(paint); } void GraphicsContext::setupStrokePaint(SkPaint* paint) { m_data->setup_paint_stroke(paint, NULL); } bool GraphicsContext::setupShadowPaint(SkPaint* paint, SkPoint* offset) { return m_data->mState->setupShadowPaint(paint, offset); } // referenced from CanvasStyle.cpp void GraphicsContext::setCMYKAFillColor(float c, float m, float y, float k, float a) { float r = 1 - (c + k); float g = 1 - (m + k); float b = 1 - (y + k); return this->setFillColor(Color(r, g, b, a)); } // referenced from CanvasStyle.cpp void GraphicsContext::setCMYKAStrokeColor(float c, float m, float y, float k, float a) { float r = 1 - (c + k); float g = 1 - (m + k); float b = 1 - (y + k); return this->setStrokeColor(Color(r, g, b, a)); } void GraphicsContext::setPlatformStrokeColor(const Color& c) { m_data->setStrokeColor(c); } void GraphicsContext::setPlatformStrokeThickness(float f) { m_data->setStrokeThickness(f); } void GraphicsContext::setPlatformFillColor(const Color& c) { m_data->setFillColor(c); } void GraphicsContext::setPlatformShadow(const IntSize& size, int blur, const Color& color) { if (paintingDisabled()) return; if (blur <= 0) { this->clearPlatformShadow(); } SkColor c; if (color.isValid()) { c = color.rgb(); } else { c = SkColorSetARGB(0xFF/3, 0, 0, 0); // "std" Apple shadow color } m_data->mState->setShadow(blur, size.width(), size.height(), c); } void GraphicsContext::clearPlatformShadow() { if (paintingDisabled()) return; m_data->mState->setShadow(0, 0, 0, 0); } /////////////////////////////////////////////////////////////////////////////// void GraphicsContext::drawFocusRing(const Color& color) { // Do nothing, since we draw the focus ring independently. } PlatformGraphicsContext* GraphicsContext::platformContext() const { ASSERT(!paintingDisabled()); return m_data->mPgc; } void GraphicsContext::setMiterLimit(float limit) { m_data->mState->mMiterLimit = limit; } void GraphicsContext::setAlpha(float alpha) { m_data->mState->mAlpha = alpha; } void GraphicsContext::setCompositeOperation(CompositeOperator op) { m_data->mState->mMode = WebCoreCompositeToSkiaComposite(op); } void GraphicsContext::clearRect(const FloatRect& rect) { if (paintingDisabled()) return; SkPaint paint; m_data->setup_paint_fill(&paint); paint.setXfermodeMode(SkXfermode::kClear_Mode); GC2Canvas(this)->drawRect(rect, paint); } void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth) { if (paintingDisabled()) return; SkPaint paint; m_data->setup_paint_stroke(&paint, NULL); paint.setStrokeWidth(SkFloatToScalar(lineWidth)); GC2Canvas(this)->drawRect(rect, paint); } void GraphicsContext::setLineCap(LineCap cap) { switch (cap) { case ButtCap: m_data->mState->mLineCap = SkPaint::kButt_Cap; break; case RoundCap: m_data->mState->mLineCap = SkPaint::kRound_Cap; break; case SquareCap: m_data->mState->mLineCap = SkPaint::kSquare_Cap; break; default: SkDEBUGF(("GraphicsContext::setLineCap: unknown LineCap %d\n", cap)); break; } } #if ENABLE(SVG) void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset) { if (paintingDisabled()) return; size_t dashLength = dashes.size(); if (!dashLength) return; size_t count = (dashLength % 2) == 0 ? dashLength : dashLength * 2; SkScalar* intervals = new SkScalar[count]; for (unsigned int i = 0; i < count; i++) intervals[i] = SkFloatToScalar(dashes[i % dashLength]); SkPathEffect **effectPtr = &m_data->mState->mPathEffect; (*effectPtr)->safeUnref(); *effectPtr = new SkDashPathEffect(intervals, count, SkFloatToScalar(dashOffset)); delete[] intervals; } #endif void GraphicsContext::setLineJoin(LineJoin join) { switch (join) { case MiterJoin: m_data->mState->mLineJoin = SkPaint::kMiter_Join; break; case RoundJoin: m_data->mState->mLineJoin = SkPaint::kRound_Join; break; case BevelJoin: m_data->mState->mLineJoin = SkPaint::kBevel_Join; break; default: SkDEBUGF(("GraphicsContext::setLineJoin: unknown LineJoin %d\n", join)); break; } } void GraphicsContext::scale(const FloatSize& size) { if (paintingDisabled()) return; GC2Canvas(this)->scale(SkFloatToScalar(size.width()), SkFloatToScalar(size.height())); } void GraphicsContext::rotate(float angleInRadians) { if (paintingDisabled()) return; GC2Canvas(this)->rotate(SkFloatToScalar(angleInRadians * (180.0f / 3.14159265f))); } void GraphicsContext::translate(float x, float y) { if (paintingDisabled()) return; GC2Canvas(this)->translate(SkFloatToScalar(x), SkFloatToScalar(y)); } void GraphicsContext::concatCTM(const TransformationMatrix& xform) { if (paintingDisabled()) return; //printf("-------------- GraphicsContext::concatCTM\n"); GC2Canvas(this)->concat((SkMatrix) xform); } FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect) { if (paintingDisabled()) return FloatRect(); const SkMatrix& matrix = GC2Canvas(this)->getTotalMatrix(); SkRect r(rect); matrix.mapRect(&r); FloatRect result(SkScalarToFloat(r.fLeft), SkScalarToFloat(r.fTop), SkScalarToFloat(r.width()), SkScalarToFloat(r.height())); return result; } ////////////////////////////////////////////////////////////////////////////////////////////////// void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect) { // appears to be PDF specific, so we ignore it #if 0 if (paintingDisabled()) return; CFURLRef urlRef = link.createCFURL(); if (urlRef) { CGContextRef context = platformContext(); // Get the bounding box to handle clipping. CGRect box = CGContextGetClipBoundingBox(context); IntRect intBox((int)box.origin.x, (int)box.origin.y, (int)box.size.width, (int)box.size.height); IntRect rect = destRect; rect.intersect(intBox); CGPDFContextSetURLForRect(context, urlRef, CGRectApplyAffineTransform(rect, CGContextGetCTM(context))); CFRelease(urlRef); } #endif } void GraphicsContext::setPlatformShouldAntialias(bool useAA) { if (paintingDisabled()) return; m_data->mState->mUseAA = useAA; } TransformationMatrix GraphicsContext::getCTM() const { const SkMatrix& m = GC2Canvas(this)->getTotalMatrix(); return TransformationMatrix(SkScalarToDouble(m.getScaleX()), // a SkScalarToDouble(m.getSkewY()), // b SkScalarToDouble(m.getSkewX()), // c SkScalarToDouble(m.getScaleY()), // d SkScalarToDouble(m.getTranslateX()), // e SkScalarToDouble(m.getTranslateY())); // f } /////////////////////////////////////////////////////////////////////////////// void GraphicsContext::beginPath() { m_data->beginPath(); } void GraphicsContext::addPath(const Path& p) { m_data->addPath(*p.platformPath()); } void GraphicsContext::fillPath() { SkPath* path = m_data->getPath(); if (paintingDisabled() || !path) return; switch (this->fillRule()) { case RULE_NONZERO: path->setFillType(SkPath::kWinding_FillType); break; case RULE_EVENODD: path->setFillType(SkPath::kEvenOdd_FillType); break; } SkPaint paint; m_data->setup_paint_fill(&paint); extactShader(&paint, m_common->state.fillColorSpace, m_common->state.fillPattern.get(), m_common->state.fillGradient.get()); GC2Canvas(this)->drawPath(*path, paint); } void GraphicsContext::strokePath() { const SkPath* path = m_data->getPath(); if (paintingDisabled() || !path) return; SkPaint paint; m_data->setup_paint_stroke(&paint, NULL); extactShader(&paint, m_common->state.strokeColorSpace, m_common->state.strokePattern.get(), m_common->state.strokeGradient.get()); GC2Canvas(this)->drawPath(*path, paint); } void GraphicsContext::setImageInterpolationQuality(InterpolationQuality mode) { /* enum InterpolationQuality { InterpolationDefault, InterpolationNone, InterpolationLow, InterpolationMedium, InterpolationHigh }; TODO: record this, so we can know when to use bitmap-filtering when we draw ... not sure how meaningful this will be given our playback model. Certainly safe to do nothing for the present. */ } } // namespace WebCore /////////////////////////////////////////////////////////////////////////////// SkCanvas* android_gc2canvas(WebCore::GraphicsContext* gc) { return gc->platformContext()->mCanvas; }