| /* |
| * Copyright (C) 2003, 2004, 2005, 2006, 2009 Apple Inc. All rights reserved. |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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 APPLE INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 "sky/engine/config.h" |
| #include "sky/engine/platform/graphics/GraphicsContext.h" |
| |
| #include "sky/engine/platform/TraceEvent.h" |
| #include "sky/engine/platform/geometry/IntRect.h" |
| #include "sky/engine/platform/geometry/RoundedRect.h" |
| #include "sky/engine/platform/graphics/BitmapImage.h" |
| #include "sky/engine/platform/graphics/DisplayList.h" |
| #include "sky/engine/platform/graphics/Gradient.h" |
| #include "sky/engine/platform/graphics/ImageBuffer.h" |
| #include "sky/engine/platform/graphics/skia/SkiaUtils.h" |
| #include "sky/engine/platform/text/BidiResolver.h" |
| #include "sky/engine/platform/text/TextRunIterator.h" |
| #include "sky/engine/platform/weborigin/KURL.h" |
| #include "sky/engine/wtf/Assertions.h" |
| #include "sky/engine/wtf/MathExtras.h" |
| #include "third_party/skia/include/core/SkAnnotation.h" |
| #include "third_party/skia/include/core/SkClipStack.h" |
| #include "third_party/skia/include/core/SkColorFilter.h" |
| #include "third_party/skia/include/core/SkData.h" |
| #include "third_party/skia/include/core/SkDevice.h" |
| #include "third_party/skia/include/core/SkPicture.h" |
| #include "third_party/skia/include/core/SkRRect.h" |
| #include "third_party/skia/include/core/SkRefCnt.h" |
| #include "third_party/skia/include/core/SkSurface.h" |
| #include "third_party/skia/include/effects/SkBlurMaskFilter.h" |
| #include "third_party/skia/include/effects/SkCornerPathEffect.h" |
| #include "third_party/skia/include/effects/SkLumaColorFilter.h" |
| #include "third_party/skia/include/effects/SkMatrixImageFilter.h" |
| #include "third_party/skia/include/effects/SkPictureImageFilter.h" |
| #include "third_party/skia/include/gpu/GrRenderTarget.h" |
| #include "third_party/skia/include/gpu/GrTexture.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| class CompatibleImageBufferSurface : public ImageBufferSurface { |
| WTF_MAKE_NONCOPYABLE(CompatibleImageBufferSurface); WTF_MAKE_FAST_ALLOCATED; |
| public: |
| CompatibleImageBufferSurface(PassRefPtr<SkSurface> surface, const IntSize& size, OpacityMode opacityMode) |
| : ImageBufferSurface(size, opacityMode) |
| , m_surface(surface) |
| { |
| } |
| virtual ~CompatibleImageBufferSurface() { } |
| |
| virtual SkCanvas* canvas() const override { return m_surface ? m_surface->getCanvas() : 0; } |
| virtual bool isValid() const override { return m_surface; } |
| virtual bool isAccelerated() const override { return isValid() && m_surface->getCanvas()->getTopDevice()->accessRenderTarget(); } |
| virtual Platform3DObject getBackingTexture() const override |
| { |
| ASSERT(isAccelerated()); |
| GrRenderTarget* renderTarget = m_surface->getCanvas()->getTopDevice()->accessRenderTarget(); |
| if (renderTarget) { |
| return renderTarget->asTexture()->getTextureHandle(); |
| } |
| return 0; |
| }; |
| |
| private: |
| RefPtr<SkSurface> m_surface; |
| }; |
| |
| } // unnamed namespace |
| |
| struct GraphicsContext::CanvasSaveState { |
| CanvasSaveState(bool pendingSave, int count) |
| : m_pendingSave(pendingSave), m_restoreCount(count) { } |
| |
| bool m_pendingSave; |
| int m_restoreCount; |
| }; |
| |
| struct GraphicsContext::RecordingState { |
| RecordingState(SkCanvas* currentCanvas, const SkMatrix& currentMatrix, PassRefPtr<DisplayList> displayList) |
| : m_savedCanvas(currentCanvas) |
| , m_displayList(displayList) |
| , m_savedMatrix(currentMatrix) |
| { |
| } |
| |
| SkCanvas* m_savedCanvas; |
| RefPtr<DisplayList> m_displayList; |
| const SkMatrix m_savedMatrix; |
| }; |
| |
| GraphicsContext::GraphicsContext(SkCanvas* canvas, DisabledMode disableContextOrPainting) |
| : m_canvas(canvas) |
| , m_paintStateStack() |
| , m_paintStateIndex(0) |
| , m_pendingCanvasSave(false) |
| , m_annotationMode(0) |
| #if ENABLE(ASSERT) |
| , m_annotationCount(0) |
| , m_layerCount(0) |
| , m_disableDestructionChecks(false) |
| #endif |
| , m_disabledState(disableContextOrPainting) |
| , m_deviceScaleFactor(1.0f) |
| , m_regionTrackingMode(RegionTrackingDisabled) |
| , m_trackTextRegion(false) |
| , m_accelerated(false) |
| , m_isCertainlyOpaque(true) |
| , m_antialiasHairlineImages(false) |
| , m_shouldSmoothFonts(true) |
| { |
| ASSERT(canvas); |
| |
| // FIXME: Do some tests to determine how many states are typically used, and allocate |
| // several here. |
| m_paintStateStack.append(GraphicsContextState::create()); |
| m_paintState = m_paintStateStack.last().get(); |
| } |
| |
| GraphicsContext::~GraphicsContext() |
| { |
| #if ENABLE(ASSERT) |
| if (!m_disableDestructionChecks) { |
| ASSERT(!m_paintStateIndex); |
| ASSERT(!m_paintState->saveCount()); |
| ASSERT(!m_annotationCount); |
| ASSERT(!m_layerCount); |
| ASSERT(m_recordingStateStack.isEmpty()); |
| ASSERT(m_canvasStateStack.isEmpty()); |
| } |
| #endif |
| } |
| |
| void GraphicsContext::resetCanvas(SkCanvas* canvas) |
| { |
| ASSERT(canvas); |
| m_canvas = canvas; |
| m_trackedRegion.reset(); |
| } |
| |
| void GraphicsContext::setRegionTrackingMode(RegionTrackingMode mode) |
| { |
| m_regionTrackingMode = mode; |
| if (mode == RegionTrackingOpaque) |
| m_trackedRegion.setTrackedRegionType(RegionTracker::Opaque); |
| else if (mode == RegionTrackingOverwrite) |
| m_trackedRegion.setTrackedRegionType(RegionTracker::Overwrite); |
| } |
| |
| void GraphicsContext::save() |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_paintState->incrementSaveCount(); |
| |
| m_canvasStateStack.append(CanvasSaveState(m_pendingCanvasSave, m_canvas->getSaveCount())); |
| m_pendingCanvasSave = true; |
| } |
| |
| void GraphicsContext::restore() |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (!m_paintStateIndex && !m_paintState->saveCount()) { |
| WTF_LOG_ERROR("ERROR void GraphicsContext::restore() stack is empty"); |
| return; |
| } |
| |
| if (m_paintState->saveCount()) { |
| m_paintState->decrementSaveCount(); |
| } else { |
| m_paintStateIndex--; |
| m_paintState = m_paintStateStack[m_paintStateIndex].get(); |
| } |
| |
| CanvasSaveState savedState = m_canvasStateStack.last(); |
| m_canvasStateStack.removeLast(); |
| m_pendingCanvasSave = savedState.m_pendingSave; |
| m_canvas->restoreToCount(savedState.m_restoreCount); |
| } |
| |
| void GraphicsContext::saveLayer(const SkRect* bounds, const SkPaint* paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->saveLayer(bounds, paint); |
| if (regionTrackingEnabled()) |
| m_trackedRegion.pushCanvasLayer(paint); |
| } |
| |
| void GraphicsContext::restoreLayer() |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->restore(); |
| if (regionTrackingEnabled()) |
| m_trackedRegion.popCanvasLayer(this); |
| } |
| |
| void GraphicsContext::beginAnnotation(const AnnotationList& annotations) |
| { |
| if (contextDisabled()) |
| return; |
| |
| canvas()->beginCommentGroup("GraphicsContextAnnotation"); |
| |
| AnnotationList::const_iterator end = annotations.end(); |
| for (AnnotationList::const_iterator it = annotations.begin(); it != end; ++it) |
| canvas()->addComment(it->first, it->second.ascii().data()); |
| |
| #if ENABLE(ASSERT) |
| ++m_annotationCount; |
| #endif |
| } |
| |
| void GraphicsContext::endAnnotation() |
| { |
| if (contextDisabled()) |
| return; |
| |
| ASSERT(m_annotationCount > 0); |
| canvas()->endCommentGroup(); |
| |
| #if ENABLE(ASSERT) |
| --m_annotationCount; |
| #endif |
| } |
| |
| void GraphicsContext::setStrokePattern(PassRefPtr<Pattern> pattern) |
| { |
| if (contextDisabled()) |
| return; |
| |
| ASSERT(pattern); |
| if (!pattern) { |
| setStrokeColor(Color::black); |
| return; |
| } |
| mutableState()->setStrokePattern(pattern); |
| } |
| |
| void GraphicsContext::setStrokeGradient(PassRefPtr<Gradient> gradient) |
| { |
| if (contextDisabled()) |
| return; |
| |
| ASSERT(gradient); |
| if (!gradient) { |
| setStrokeColor(Color::black); |
| return; |
| } |
| mutableState()->setStrokeGradient(gradient); |
| } |
| |
| void GraphicsContext::setFillPattern(PassRefPtr<Pattern> pattern) |
| { |
| if (contextDisabled()) |
| return; |
| |
| ASSERT(pattern); |
| if (!pattern) { |
| setFillColor(Color::black); |
| return; |
| } |
| |
| mutableState()->setFillPattern(pattern); |
| } |
| |
| void GraphicsContext::setFillGradient(PassRefPtr<Gradient> gradient) |
| { |
| if (contextDisabled()) |
| return; |
| |
| ASSERT(gradient); |
| if (!gradient) { |
| setFillColor(Color::black); |
| return; |
| } |
| |
| mutableState()->setFillGradient(gradient); |
| } |
| |
| void GraphicsContext::setShadow(const FloatSize& offset, float blur, const Color& color, |
| DrawLooperBuilder::ShadowTransformMode shadowTransformMode, |
| DrawLooperBuilder::ShadowAlphaMode shadowAlphaMode) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (!color.alpha() || (!offset.width() && !offset.height() && !blur)) { |
| clearShadow(); |
| return; |
| } |
| |
| OwnPtr<DrawLooperBuilder> drawLooperBuilder = DrawLooperBuilder::create(); |
| drawLooperBuilder->addShadow(offset, blur, color, shadowTransformMode, shadowAlphaMode); |
| drawLooperBuilder->addUnmodifiedContent(); |
| setDrawLooper(drawLooperBuilder.release()); |
| } |
| |
| void GraphicsContext::setDrawLooper(PassOwnPtr<DrawLooperBuilder> drawLooperBuilder) |
| { |
| if (contextDisabled()) |
| return; |
| |
| mutableState()->setDrawLooper(drawLooperBuilder->detachDrawLooper()); |
| } |
| |
| void GraphicsContext::clearDrawLooper() |
| { |
| if (contextDisabled()) |
| return; |
| |
| mutableState()->clearDrawLooper(); |
| } |
| |
| bool GraphicsContext::hasShadow() const |
| { |
| return !!immutableState()->drawLooper(); |
| } |
| |
| bool GraphicsContext::getTransformedClipBounds(FloatRect* bounds) const |
| { |
| if (contextDisabled()) |
| return false; |
| SkIRect skIBounds; |
| if (!m_canvas->getClipDeviceBounds(&skIBounds)) |
| return false; |
| SkRect skBounds = SkRect::Make(skIBounds); |
| *bounds = FloatRect(skBounds); |
| return true; |
| } |
| |
| SkMatrix GraphicsContext::getTotalMatrix() const |
| { |
| if (contextDisabled()) |
| return SkMatrix::I(); |
| |
| if (!isRecording()) |
| return m_canvas->getTotalMatrix(); |
| |
| const RecordingState& recordingState = m_recordingStateStack.last(); |
| SkMatrix totalMatrix = recordingState.m_savedMatrix; |
| totalMatrix.preConcat(m_canvas->getTotalMatrix()); |
| |
| return totalMatrix; |
| } |
| |
| void GraphicsContext::adjustTextRenderMode(SkPaint* paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (!paint->isLCDRenderText()) |
| return; |
| |
| paint->setLCDRenderText(couldUseLCDRenderedText()); |
| } |
| |
| bool GraphicsContext::couldUseLCDRenderedText() |
| { |
| // Our layers only have a single alpha channel. This means that subpixel |
| // rendered text cannot be composited correctly when the layer is |
| // collapsed. Therefore, subpixel text is contextDisabled when we are drawing |
| // onto a layer. |
| if (contextDisabled() || m_canvas->isDrawingToLayer() || !isCertainlyOpaque()) |
| return false; |
| |
| return shouldSmoothFonts(); |
| } |
| |
| void GraphicsContext::setCompositeOperation(CompositeOperator compositeOperation, WebBlendMode blendMode) |
| { |
| if (contextDisabled()) |
| return; |
| mutableState()->setCompositeOperation(compositeOperation, blendMode); |
| } |
| |
| SkColorFilter* GraphicsContext::colorFilter() const |
| { |
| return immutableState()->colorFilter(); |
| } |
| |
| void GraphicsContext::setColorFilter(ColorFilter colorFilter) |
| { |
| GraphicsContextState* stateToSet = mutableState(); |
| |
| // We only support one active color filter at the moment. If (when) this becomes a problem, |
| // we should switch to using color filter chains (Skia work in progress). |
| ASSERT(!stateToSet->colorFilter()); |
| stateToSet->setColorFilter(WebCoreColorFilterToSkiaColorFilter(colorFilter)); |
| } |
| |
| bool GraphicsContext::readPixels(const SkImageInfo& info, void* pixels, size_t rowBytes, int x, int y) |
| { |
| if (contextDisabled()) |
| return false; |
| |
| return m_canvas->readPixels(info, pixels, rowBytes, x, y); |
| } |
| |
| void GraphicsContext::setMatrix(const SkMatrix& matrix) |
| { |
| if (contextDisabled()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->setMatrix(matrix); |
| } |
| |
| void GraphicsContext::concat(const SkMatrix& matrix) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (matrix.isIdentity()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->concat(matrix); |
| } |
| |
| void GraphicsContext::beginTransparencyLayer(float opacity, const FloatRect* bounds) |
| { |
| beginLayer(opacity, immutableState()->compositeOperator(), bounds); |
| } |
| |
| void GraphicsContext::beginLayer(float opacity, CompositeOperator op, const FloatRect* bounds, ColorFilter colorFilter, ImageFilter* imageFilter) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkPaint layerPaint; |
| layerPaint.setAlpha(static_cast<unsigned char>(opacity * 255)); |
| layerPaint.setXfermodeMode(WebCoreCompositeToSkiaComposite(op, m_paintState->blendMode())); |
| layerPaint.setColorFilter(WebCoreColorFilterToSkiaColorFilter(colorFilter).get()); |
| layerPaint.setImageFilter(imageFilter); |
| |
| if (bounds) { |
| SkRect skBounds = WebCoreFloatRectToSKRect(*bounds); |
| saveLayer(&skBounds, &layerPaint); |
| } else { |
| saveLayer(0, &layerPaint); |
| } |
| |
| #if ENABLE(ASSERT) |
| ++m_layerCount; |
| #endif |
| } |
| |
| void GraphicsContext::endLayer() |
| { |
| if (contextDisabled()) |
| return; |
| |
| restoreLayer(); |
| |
| ASSERT(m_layerCount > 0); |
| #if ENABLE(ASSERT) |
| --m_layerCount; |
| #endif |
| } |
| |
| void GraphicsContext::beginRecording(const FloatRect& bounds) |
| { |
| RefPtr<DisplayList> displayList = adoptRef(new DisplayList(bounds)); |
| |
| SkCanvas* savedCanvas = m_canvas; |
| SkMatrix savedMatrix = getTotalMatrix(); |
| |
| if (!contextDisabled()) { |
| IntRect recordingRect = enclosingIntRect(bounds); |
| m_canvas = displayList->beginRecording(recordingRect.size()); |
| |
| // We want the bounds offset mapped to (0, 0), such that the display list content |
| // is fully contained within the SkPictureRecord's bounds. |
| if (!toFloatSize(bounds.location()).isZero()) { |
| m_canvas->translate(-bounds.x(), -bounds.y()); |
| // To avoid applying the offset repeatedly in getTotalMatrix(), we pre-apply it here. |
| savedMatrix.preTranslate(bounds.x(), bounds.y()); |
| } |
| } |
| |
| m_recordingStateStack.append(RecordingState(savedCanvas, savedMatrix, displayList)); |
| } |
| |
| PassRefPtr<DisplayList> GraphicsContext::endRecording() |
| { |
| ASSERT(!m_recordingStateStack.isEmpty()); |
| |
| RecordingState recording = m_recordingStateStack.last(); |
| if (!contextDisabled()) { |
| ASSERT(recording.m_displayList->isRecording()); |
| recording.m_displayList->endRecording(); |
| } |
| |
| m_recordingStateStack.removeLast(); |
| m_canvas = recording.m_savedCanvas; |
| |
| return recording.m_displayList.release(); |
| } |
| |
| bool GraphicsContext::isRecording() const |
| { |
| return !m_recordingStateStack.isEmpty(); |
| } |
| |
| void GraphicsContext::drawDisplayList(DisplayList* displayList) |
| { |
| ASSERT(displayList); |
| ASSERT(!displayList->isRecording()); |
| |
| if (contextDisabled() || displayList->bounds().isEmpty()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| const FloatRect& bounds = displayList->bounds(); |
| if (bounds.x() || bounds.y()) { |
| SkMatrix m; |
| m.setTranslate(bounds.x(), bounds.y()); |
| m_canvas->drawPicture(displayList->picture(), &m, 0); |
| } else { |
| m_canvas->drawPicture(displayList->picture()); |
| } |
| } |
| |
| void GraphicsContext::drawConvexPolygon(size_t numPoints, const FloatPoint* points, bool shouldAntialias) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (numPoints <= 1) |
| return; |
| |
| SkPath path; |
| setPathFromConvexPoints(&path, numPoints, points); |
| |
| SkPaint paint(immutableState()->fillPaint()); |
| paint.setAntiAlias(shouldAntialias); |
| drawPath(path, paint); |
| |
| if (strokeStyle() != NoStroke) |
| drawPath(path, immutableState()->strokePaint()); |
| } |
| |
| float GraphicsContext::prepareFocusRingPaint(SkPaint& paint, const Color& color, int width) const |
| { |
| paint.setAntiAlias(true); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setColor(color.rgb()); |
| paint.setStrokeWidth(focusRingWidth(width)); |
| return 1; |
| } |
| |
| void GraphicsContext::drawFocusRingPath(const SkPath& path, const Color& color, int width) |
| { |
| SkPaint paint; |
| float cornerRadius = prepareFocusRingPaint(paint, color, width); |
| |
| paint.setPathEffect(SkCornerPathEffect::Create(SkFloatToScalar(cornerRadius)))->unref(); |
| |
| // Outer path |
| drawPath(path, paint); |
| } |
| |
| void GraphicsContext::drawFocusRingRect(const SkRect& rect, const Color& color, int width) |
| { |
| SkPaint paint; |
| float cornerRadius = prepareFocusRingPaint(paint, color, width); |
| |
| SkRRect rrect; |
| rrect.setRectXY(rect, SkFloatToScalar(cornerRadius), SkFloatToScalar(cornerRadius)); |
| |
| // Outer rect |
| drawRRect(rrect, paint); |
| } |
| |
| void GraphicsContext::drawFocusRing(const Path& focusRingPath, int width, int offset, const Color& color) |
| { |
| // FIXME: Implement support for offset. |
| if (contextDisabled()) |
| return; |
| |
| drawFocusRingPath(focusRingPath.skPath(), color, width); |
| } |
| |
| void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int width, int offset, const Color& color) |
| { |
| if (contextDisabled()) |
| return; |
| |
| unsigned rectCount = rects.size(); |
| if (!rectCount) |
| return; |
| |
| SkRegion focusRingRegion; |
| const int outset = focusRingOutset(offset); |
| for (unsigned i = 0; i < rectCount; i++) { |
| SkIRect r = rects[i]; |
| r.inset(-outset, -outset); |
| focusRingRegion.op(r, SkRegion::kUnion_Op); |
| } |
| |
| if (focusRingRegion.isRect()) { |
| drawFocusRingRect(SkRect::MakeFromIRect(focusRingRegion.getBounds()), color, width); |
| } else { |
| SkPath path; |
| if (focusRingRegion.getBoundaryPath(&path)) |
| drawFocusRingPath(path, color, width); |
| } |
| } |
| |
| static inline IntRect areaCastingShadowInHole(const IntRect& holeRect, int shadowBlur, int shadowSpread, const IntSize& shadowOffset) |
| { |
| IntRect bounds(holeRect); |
| |
| bounds.inflate(shadowBlur); |
| |
| if (shadowSpread < 0) |
| bounds.inflate(-shadowSpread); |
| |
| IntRect offsetBounds = bounds; |
| offsetBounds.move(-shadowOffset); |
| return unionRect(bounds, offsetBounds); |
| } |
| |
| void GraphicsContext::drawInnerShadow(const RoundedRect& rect, const Color& shadowColor, const IntSize shadowOffset, int shadowBlur, int shadowSpread, Edges clippedEdges) |
| { |
| if (contextDisabled()) |
| return; |
| |
| IntRect holeRect(rect.rect()); |
| holeRect.inflate(-shadowSpread); |
| |
| if (holeRect.isEmpty()) { |
| if (rect.isRounded()) |
| fillRoundedRect(rect, shadowColor); |
| else |
| fillRect(rect.rect(), shadowColor); |
| return; |
| } |
| |
| if (clippedEdges & LeftEdge) { |
| holeRect.move(-std::max(shadowOffset.width(), 0) - shadowBlur, 0); |
| holeRect.setWidth(holeRect.width() + std::max(shadowOffset.width(), 0) + shadowBlur); |
| } |
| if (clippedEdges & TopEdge) { |
| holeRect.move(0, -std::max(shadowOffset.height(), 0) - shadowBlur); |
| holeRect.setHeight(holeRect.height() + std::max(shadowOffset.height(), 0) + shadowBlur); |
| } |
| if (clippedEdges & RightEdge) |
| holeRect.setWidth(holeRect.width() - std::min(shadowOffset.width(), 0) + shadowBlur); |
| if (clippedEdges & BottomEdge) |
| holeRect.setHeight(holeRect.height() - std::min(shadowOffset.height(), 0) + shadowBlur); |
| |
| Color fillColor(shadowColor.red(), shadowColor.green(), shadowColor.blue(), 255); |
| |
| IntRect outerRect = areaCastingShadowInHole(rect.rect(), shadowBlur, shadowSpread, shadowOffset); |
| RoundedRect roundedHole(holeRect, rect.radii()); |
| |
| save(); |
| if (rect.isRounded()) { |
| Path path; |
| path.addRoundedRect(rect); |
| clipPath(path); |
| roundedHole.shrinkRadii(shadowSpread); |
| } else { |
| clip(rect.rect()); |
| } |
| |
| OwnPtr<DrawLooperBuilder> drawLooperBuilder = DrawLooperBuilder::create(); |
| drawLooperBuilder->addShadow(shadowOffset, shadowBlur, shadowColor, |
| DrawLooperBuilder::ShadowRespectsTransforms, DrawLooperBuilder::ShadowIgnoresAlpha); |
| setDrawLooper(drawLooperBuilder.release()); |
| fillRectWithRoundedHole(outerRect, roundedHole, fillColor); |
| restore(); |
| clearDrawLooper(); |
| } |
| |
| void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2) |
| { |
| if (contextDisabled()) |
| return; |
| |
| StrokeStyle penStyle = strokeStyle(); |
| if (penStyle == NoStroke) |
| return; |
| |
| FloatPoint p1 = point1; |
| FloatPoint p2 = point2; |
| bool isVerticalLine = (p1.x() == p2.x()); |
| int width = roundf(strokeThickness()); |
| |
| // We know these are vertical or horizontal lines, so the length will just |
| // be the sum of the displacement component vectors give or take 1 - |
| // probably worth the speed up of no square root, which also won't be exact. |
| FloatSize disp = p2 - p1; |
| int length = SkScalarRoundToInt(disp.width() + disp.height()); |
| SkPaint paint(immutableState()->strokePaint(length)); |
| |
| if (strokeStyle() == DottedStroke || strokeStyle() == DashedStroke) { |
| // Do a rect fill of our endpoints. This ensures we always have the |
| // appearance of being a border. We then draw the actual dotted/dashed line. |
| SkRect r1, r2; |
| r1.set(p1.x(), p1.y(), p1.x() + width, p1.y() + width); |
| r2.set(p2.x(), p2.y(), p2.x() + width, p2.y() + width); |
| |
| if (isVerticalLine) { |
| r1.offset(-width / 2, 0); |
| r2.offset(-width / 2, -width); |
| } else { |
| r1.offset(0, -width / 2); |
| r2.offset(-width, -width / 2); |
| } |
| SkPaint fillPaint; |
| fillPaint.setColor(paint.getColor()); |
| drawRect(r1, fillPaint); |
| drawRect(r2, fillPaint); |
| } |
| |
| adjustLineToPixelBoundaries(p1, p2, width, penStyle); |
| SkPoint pts[2] = { p1.data(), p2.data() }; |
| |
| m_canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, paint); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawPoints(this, SkCanvas::kLines_PointMode, 2, pts, paint); |
| } |
| |
| void GraphicsContext::drawLineForDocumentMarker(const FloatPoint& pt, float width, DocumentMarkerLineStyle style) |
| { |
| if (contextDisabled()) |
| return; |
| |
| // Use 2x resources for a device scale factor of 1.5 or above. |
| int deviceScaleFactor = m_deviceScaleFactor > 1.5f ? 2 : 1; |
| |
| // Create the pattern we'll use to draw the underline. |
| int index = style == DocumentMarkerGrammarLineStyle ? 1 : 0; |
| static SkBitmap* misspellBitmap1x[2] = { 0, 0 }; |
| static SkBitmap* misspellBitmap2x[2] = { 0, 0 }; |
| SkBitmap** misspellBitmap = deviceScaleFactor == 2 ? misspellBitmap2x : misspellBitmap1x; |
| if (!misspellBitmap[index]) { |
| // We use a 2-pixel-high misspelling indicator because that seems to be |
| // what WebKit is designed for, and how much room there is in a typical |
| // page for it. |
| const int rowPixels = 32 * deviceScaleFactor; // Must be multiple of 4 for pattern below. |
| const int colPixels = 2 * deviceScaleFactor; |
| SkBitmap bitmap; |
| bitmap.allocN32Pixels(rowPixels, colPixels); |
| |
| bitmap.eraseARGB(0, 0, 0, 0); |
| if (deviceScaleFactor == 1) |
| draw1xMarker(&bitmap, index); |
| else if (deviceScaleFactor == 2) |
| draw2xMarker(&bitmap, index); |
| else |
| ASSERT_NOT_REACHED(); |
| |
| misspellBitmap[index] = new SkBitmap(bitmap); |
| } |
| |
| SkScalar originX = WebCoreFloatToSkScalar(pt.x()); |
| |
| // Offset it vertically by 1 so that there's some space under the text. |
| SkScalar originY = WebCoreFloatToSkScalar(pt.y()) + 1; |
| originX *= deviceScaleFactor; |
| originY *= deviceScaleFactor; |
| |
| SkMatrix localMatrix; |
| localMatrix.setTranslate(originX, originY); |
| RefPtr<SkShader> shader = adoptRef(SkShader::CreateBitmapShader( |
| *misspellBitmap[index], SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode, &localMatrix)); |
| |
| SkPaint paint; |
| paint.setShader(shader.get()); |
| |
| SkRect rect; |
| rect.set(originX, originY, originX + WebCoreFloatToSkScalar(width) * deviceScaleFactor, originY + SkIntToScalar(misspellBitmap[index]->height())); |
| |
| if (deviceScaleFactor == 2) { |
| save(); |
| scale(0.5, 0.5); |
| } |
| drawRect(rect, paint); |
| if (deviceScaleFactor == 2) |
| restore(); |
| } |
| |
| void GraphicsContext::drawLineForText(const FloatPoint& pt, float width) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (width <= 0) |
| return; |
| |
| SkPaint paint; |
| switch (strokeStyle()) { |
| case NoStroke: |
| case SolidStroke: |
| case DoubleStroke: |
| case WavyStroke: { |
| int thickness = SkMax32(static_cast<int>(strokeThickness()), 1); |
| SkRect r; |
| r.fLeft = WebCoreFloatToSkScalar(pt.x()); |
| // Avoid anti-aliasing lines. Currently, these are always horizontal. |
| // Round to nearest pixel to match text and other content. |
| r.fTop = WebCoreFloatToSkScalar(floorf(pt.y() + 0.5f)); |
| r.fRight = r.fLeft + WebCoreFloatToSkScalar(width); |
| r.fBottom = r.fTop + SkIntToScalar(thickness); |
| paint = immutableState()->fillPaint(); |
| // Text lines are drawn using the stroke color. |
| paint.setColor(effectiveStrokeColor()); |
| drawRect(r, paint); |
| return; |
| } |
| case DottedStroke: |
| case DashedStroke: { |
| int y = floorf(pt.y() + std::max<float>(strokeThickness() / 2.0f, 0.5f)); |
| drawLine(IntPoint(pt.x(), y), IntPoint(pt.x() + width, y)); |
| return; |
| } |
| } |
| |
| ASSERT_NOT_REACHED(); |
| } |
| |
| // Draws a filled rectangle with a stroked border. |
| void GraphicsContext::drawRect(const IntRect& rect) |
| { |
| if (contextDisabled()) |
| return; |
| |
| ASSERT(!rect.isEmpty()); |
| if (rect.isEmpty()) |
| return; |
| |
| SkRect skRect = rect; |
| int fillcolorNotTransparent = immutableState()->fillColor().rgb() & 0xFF000000; |
| if (fillcolorNotTransparent) |
| drawRect(skRect, immutableState()->fillPaint()); |
| |
| if (immutableState()->strokeData().style() != NoStroke |
| && immutableState()->strokeData().color().alpha()) { |
| // Stroke a width: 1 inset border |
| SkPaint paint(immutableState()->fillPaint()); |
| paint.setColor(effectiveStrokeColor()); |
| paint.setStyle(SkPaint::kStroke_Style); |
| paint.setStrokeWidth(1); |
| |
| skRect.inset(0.5f, 0.5f); |
| drawRect(skRect, paint); |
| } |
| } |
| |
| void GraphicsContext::drawText(const Font& font, const TextRunPaintInfo& runInfo, const FloatPoint& point) |
| { |
| if (contextDisabled()) |
| return; |
| |
| font.drawText(this, runInfo, point); |
| } |
| |
| void GraphicsContext::drawEmphasisMarks(const Font& font, const TextRunPaintInfo& runInfo, const AtomicString& mark, const FloatPoint& point) |
| { |
| if (contextDisabled()) |
| return; |
| |
| font.drawEmphasisMarks(this, runInfo, mark, point); |
| } |
| |
| void GraphicsContext::drawBidiText(const Font& font, const TextRunPaintInfo& runInfo, const FloatPoint& point, Font::CustomFontNotReadyAction customFontNotReadyAction) |
| { |
| if (contextDisabled()) |
| return; |
| |
| // sub-run painting is not supported for Bidi text. |
| const TextRun& run = runInfo.run; |
| ASSERT((runInfo.from == 0) && (runInfo.to == run.length())); |
| BidiResolver<TextRunIterator, BidiCharacterRun> bidiResolver; |
| bidiResolver.setStatus(BidiStatus(run.direction(), run.directionalOverride())); |
| bidiResolver.setPositionIgnoringNestedIsolates(TextRunIterator(&run, 0)); |
| |
| // FIXME: This ownership should be reversed. We should pass BidiRunList |
| // to BidiResolver in createBidiRunsForLine. |
| BidiRunList<BidiCharacterRun>& bidiRuns = bidiResolver.runs(); |
| bidiResolver.createBidiRunsForLine(TextRunIterator(&run, run.length())); |
| if (!bidiRuns.runCount()) |
| return; |
| |
| FloatPoint currPoint = point; |
| BidiCharacterRun* bidiRun = bidiRuns.firstRun(); |
| while (bidiRun) { |
| TextRun subrun = run.subRun(bidiRun->start(), bidiRun->stop() - bidiRun->start()); |
| bool isRTL = bidiRun->level() % 2; |
| subrun.setDirection(isRTL ? RTL : LTR); |
| subrun.setDirectionalOverride(bidiRun->dirOverride()); |
| |
| TextRunPaintInfo subrunInfo(subrun); |
| subrunInfo.bounds = runInfo.bounds; |
| font.drawText(this, subrunInfo, currPoint, customFontNotReadyAction); |
| |
| bidiRun = bidiRun->next(); |
| // FIXME: Have Font::drawText return the width of what it drew so that we don't have to re-measure here. |
| if (bidiRun) |
| currPoint.move(font.width(subrun), 0); |
| } |
| |
| bidiRuns.deleteRuns(); |
| } |
| |
| void GraphicsContext::drawHighlightForText(const Font& font, const TextRun& run, const FloatPoint& point, int h, const Color& backgroundColor, int from, int to) |
| { |
| if (contextDisabled()) |
| return; |
| |
| fillRect(font.selectionRectForText(run, point, h, from, to), backgroundColor); |
| } |
| |
| void GraphicsContext::drawImage(Image* image, const IntPoint& p, CompositeOperator op, RespectImageOrientationEnum shouldRespectImageOrientation) |
| { |
| if (!image) |
| return; |
| drawImage(image, FloatRect(IntRect(p, image->size())), FloatRect(FloatPoint(), FloatSize(image->size())), op, shouldRespectImageOrientation); |
| } |
| |
| void GraphicsContext::drawImage(Image* image, const IntRect& r, CompositeOperator op, RespectImageOrientationEnum shouldRespectImageOrientation) |
| { |
| if (!image) |
| return; |
| drawImage(image, FloatRect(r), FloatRect(FloatPoint(), FloatSize(image->size())), op, shouldRespectImageOrientation); |
| } |
| |
| void GraphicsContext::drawImage(Image* image, const FloatRect& dest, const FloatRect& src, CompositeOperator op, RespectImageOrientationEnum shouldRespectImageOrientation) |
| { |
| drawImage(image, dest, src, op, WebBlendModeNormal, shouldRespectImageOrientation); |
| } |
| |
| void GraphicsContext::drawImage(Image* image, const FloatRect& dest) |
| { |
| if (!image) |
| return; |
| drawImage(image, dest, FloatRect(IntRect(IntPoint(), image->size()))); |
| } |
| |
| void GraphicsContext::drawImage(Image* image, const FloatRect& dest, const FloatRect& src, CompositeOperator op, WebBlendMode blendMode, RespectImageOrientationEnum shouldRespectImageOrientation) |
| { |
| if (contextDisabled() || !image) |
| return; |
| image->draw(this, dest, src, op, blendMode, shouldRespectImageOrientation); |
| } |
| |
| void GraphicsContext::drawTiledImage(Image* image, const IntRect& destRect, const IntPoint& srcPoint, const IntSize& tileSize, CompositeOperator op, WebBlendMode blendMode, const IntSize& repeatSpacing) |
| { |
| if (contextDisabled() || !image) |
| return; |
| image->drawTiled(this, destRect, srcPoint, tileSize, op, blendMode, repeatSpacing); |
| } |
| |
| void GraphicsContext::drawTiledImage(Image* image, const IntRect& dest, const IntRect& srcRect, |
| const FloatSize& tileScaleFactor, Image::TileRule hRule, Image::TileRule vRule, CompositeOperator op) |
| { |
| if (contextDisabled() || !image) |
| return; |
| |
| if (hRule == Image::StretchTile && vRule == Image::StretchTile) { |
| // Just do a scale. |
| drawImage(image, dest, srcRect, op); |
| return; |
| } |
| |
| image->drawTiled(this, dest, srcRect, tileScaleFactor, hRule, vRule, op); |
| } |
| |
| void GraphicsContext::drawImageBuffer(ImageBuffer* image, const FloatRect& dest, |
| const FloatRect* src, CompositeOperator op, WebBlendMode blendMode) |
| { |
| if (contextDisabled() || !image) |
| return; |
| |
| image->draw(this, dest, src, op, blendMode); |
| } |
| |
| void GraphicsContext::drawPicture(PassRefPtr<SkPicture> picture, const FloatRect& dest, const FloatRect& src, CompositeOperator op, WebBlendMode blendMode) |
| { |
| if (contextDisabled() || !picture) |
| return; |
| |
| SkMatrix ctm = m_canvas->getTotalMatrix(); |
| SkRect deviceDest; |
| ctm.mapRect(&deviceDest, dest); |
| SkRect sourceBounds = WebCoreFloatRectToSKRect(src); |
| |
| RefPtr<SkPictureImageFilter> pictureFilter = adoptRef(SkPictureImageFilter::Create(picture.get(), sourceBounds)); |
| SkMatrix layerScale; |
| layerScale.setScale(deviceDest.width() / src.width(), deviceDest.height() / src.height()); |
| RefPtr<SkMatrixImageFilter> matrixFilter = adoptRef(SkMatrixImageFilter::Create(layerScale, SkPaint::kLow_FilterLevel, pictureFilter.get())); |
| SkPaint picturePaint; |
| picturePaint.setXfermodeMode(WebCoreCompositeToSkiaComposite(op, blendMode)); |
| picturePaint.setImageFilter(matrixFilter.get()); |
| SkRect layerBounds = SkRect::MakeWH(std::max(deviceDest.width(), sourceBounds.width()), std::max(deviceDest.height(), sourceBounds.height())); |
| m_canvas->save(); |
| m_canvas->resetMatrix(); |
| m_canvas->translate(deviceDest.x(), deviceDest.y()); |
| m_canvas->saveLayer(&layerBounds, &picturePaint); |
| m_canvas->restore(); |
| m_canvas->restore(); |
| } |
| |
| void GraphicsContext::writePixels(const SkImageInfo& info, const void* pixels, size_t rowBytes, int x, int y) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->writePixels(info, pixels, rowBytes, x, y); |
| |
| if (regionTrackingEnabled()) { |
| SkRect rect = SkRect::MakeXYWH(x, y, info.width(), info.height()); |
| SkPaint paint; |
| |
| paint.setXfermodeMode(SkXfermode::kSrc_Mode); |
| if (kOpaque_SkAlphaType != info.alphaType()) |
| paint.setAlpha(0x80); // signal to m_trackedRegion that we are not fully opaque |
| |
| m_trackedRegion.didDrawRect(this, rect, paint, 0); |
| // more efficient would be to call markRectAsOpaque or MarkRectAsNonOpaque directly, |
| // rather than cons-ing up a paint with an xfermode and alpha |
| } |
| } |
| |
| void GraphicsContext::writePixels(const SkBitmap& bitmap, int x, int y) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (!bitmap.getTexture()) { |
| SkAutoLockPixels alp(bitmap); |
| if (bitmap.getPixels()) |
| writePixels(bitmap.info(), bitmap.getPixels(), bitmap.rowBytes(), x, y); |
| } |
| } |
| |
| void GraphicsContext::drawBitmap(const SkBitmap& bitmap, SkScalar left, SkScalar top, const SkPaint* paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawBitmap(bitmap, left, top, paint); |
| |
| if (regionTrackingEnabled()) { |
| SkRect rect = SkRect::MakeXYWH(left, top, bitmap.width(), bitmap.height()); |
| m_trackedRegion.didDrawRect(this, rect, *paint, &bitmap); |
| } |
| } |
| |
| void GraphicsContext::drawBitmapRect(const SkBitmap& bitmap, const SkRect* src, |
| const SkRect& dst, const SkPaint* paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkCanvas::DrawBitmapRectFlags flags = |
| immutableState()->shouldClampToSourceRect() ? SkCanvas::kNone_DrawBitmapRectFlag : SkCanvas::kBleed_DrawBitmapRectFlag; |
| |
| m_canvas->drawBitmapRectToRect(bitmap, src, dst, paint, flags); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawRect(this, dst, *paint, &bitmap); |
| } |
| |
| void GraphicsContext::drawOval(const SkRect& oval, const SkPaint& paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawOval(oval, paint); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawBounded(this, oval, paint); |
| } |
| |
| void GraphicsContext::drawPath(const SkPath& path, const SkPaint& paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawPath(path, paint); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawPath(this, path, paint); |
| } |
| |
| void GraphicsContext::drawRect(const SkRect& rect, const SkPaint& paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawRect(rect, paint); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawRect(this, rect, paint, 0); |
| } |
| |
| void GraphicsContext::drawRRect(const SkRRect& rrect, const SkPaint& paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawRRect(rrect, paint); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawBounded(this, rrect.rect(), paint); |
| } |
| |
| void GraphicsContext::didDrawRect(const SkRect& rect, const SkPaint& paint, const SkBitmap* bitmap) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawRect(this, rect, paint, bitmap); |
| } |
| |
| void GraphicsContext::drawPosText(const void* text, size_t byteLength, |
| const SkPoint pos[], const SkRect& textRect, const SkPaint& paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawPosText(text, byteLength, pos, paint); |
| didDrawTextInRect(textRect); |
| |
| // FIXME: compute bounds for positioned text. |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawUnbounded(this, paint, RegionTracker::FillOrStroke); |
| } |
| |
| void GraphicsContext::drawPosTextH(const void* text, size_t byteLength, |
| const SkScalar xpos[], SkScalar constY, const SkRect& textRect, const SkPaint& paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawPosTextH(text, byteLength, xpos, constY, paint); |
| didDrawTextInRect(textRect); |
| |
| // FIXME: compute bounds for positioned text. |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawUnbounded(this, paint, RegionTracker::FillOrStroke); |
| } |
| |
| void GraphicsContext::drawTextBlob(const SkTextBlob* blob, const SkPoint& origin, const SkPaint& paint) |
| { |
| if (contextDisabled()) |
| return; |
| |
| m_canvas->drawTextBlob(blob, origin.x(), origin.y(), paint); |
| |
| SkRect bounds = blob->bounds(); |
| bounds.offset(origin); |
| didDrawTextInRect(bounds); |
| |
| // FIXME: use bounds here if it helps performance. |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawUnbounded(this, paint, RegionTracker::FillOrStroke); |
| } |
| |
| void GraphicsContext::fillPath(const Path& pathToFill) |
| { |
| if (contextDisabled() || pathToFill.isEmpty()) |
| return; |
| |
| // Use const_cast and temporarily modify the fill type instead of copying the path. |
| SkPath& path = const_cast<SkPath&>(pathToFill.skPath()); |
| SkPath::FillType previousFillType = path.getFillType(); |
| |
| SkPath::FillType temporaryFillType = WebCoreWindRuleToSkFillType(immutableState()->fillRule()); |
| path.setFillType(temporaryFillType); |
| |
| drawPath(path, immutableState()->fillPaint()); |
| |
| path.setFillType(previousFillType); |
| } |
| |
| void GraphicsContext::fillRect(const FloatRect& rect) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkRect r = rect; |
| |
| drawRect(r, immutableState()->fillPaint()); |
| } |
| |
| void GraphicsContext::fillRect(const FloatRect& rect, const Color& color) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkRect r = rect; |
| SkPaint paint = immutableState()->fillPaint(); |
| paint.setColor(color.rgb()); |
| drawRect(r, paint); |
| } |
| |
| void GraphicsContext::fillBetweenRoundedRects(const IntRect& outer, const IntSize& outerTopLeft, const IntSize& outerTopRight, const IntSize& outerBottomLeft, const IntSize& outerBottomRight, |
| const IntRect& inner, const IntSize& innerTopLeft, const IntSize& innerTopRight, const IntSize& innerBottomLeft, const IntSize& innerBottomRight, const Color& color) { |
| if (contextDisabled()) |
| return; |
| |
| SkVector outerRadii[4]; |
| SkVector innerRadii[4]; |
| setRadii(outerRadii, outerTopLeft, outerTopRight, outerBottomRight, outerBottomLeft); |
| setRadii(innerRadii, innerTopLeft, innerTopRight, innerBottomRight, innerBottomLeft); |
| |
| SkRRect rrOuter; |
| SkRRect rrInner; |
| rrOuter.setRectRadii(outer, outerRadii); |
| rrInner.setRectRadii(inner, innerRadii); |
| |
| SkPaint paint(immutableState()->fillPaint()); |
| paint.setColor(color.rgb()); |
| |
| m_canvas->drawDRRect(rrOuter, rrInner, paint); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawBounded(this, rrOuter.getBounds(), paint); |
| } |
| |
| void GraphicsContext::fillBetweenRoundedRects(const RoundedRect& outer, const RoundedRect& inner, const Color& color) |
| { |
| fillBetweenRoundedRects(outer.rect(), outer.radii().topLeft(), outer.radii().topRight(), outer.radii().bottomLeft(), outer.radii().bottomRight(), |
| inner.rect(), inner.radii().topLeft(), inner.radii().topRight(), inner.radii().bottomLeft(), inner.radii().bottomRight(), color); |
| } |
| |
| void GraphicsContext::fillRoundedRect(const IntRect& rect, const IntSize& topLeft, const IntSize& topRight, |
| const IntSize& bottomLeft, const IntSize& bottomRight, const Color& color) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (topLeft.width() + topRight.width() > rect.width() |
| || bottomLeft.width() + bottomRight.width() > rect.width() |
| || topLeft.height() + bottomLeft.height() > rect.height() |
| || topRight.height() + bottomRight.height() > rect.height()) { |
| // Not all the radii fit, return a rect. This matches the behavior of |
| // Path::createRoundedRectangle. Without this we attempt to draw a round |
| // shadow for a square box. |
| fillRect(rect, color); |
| return; |
| } |
| |
| SkVector radii[4]; |
| setRadii(radii, topLeft, topRight, bottomRight, bottomLeft); |
| |
| SkRRect rr; |
| rr.setRectRadii(rect, radii); |
| |
| SkPaint paint(immutableState()->fillPaint()); |
| paint.setColor(color.rgb()); |
| |
| m_canvas->drawRRect(rr, paint); |
| |
| if (regionTrackingEnabled()) |
| m_trackedRegion.didDrawBounded(this, rr.getBounds(), paint); |
| } |
| |
| void GraphicsContext::fillEllipse(const FloatRect& ellipse) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkRect rect = ellipse; |
| drawOval(rect, immutableState()->fillPaint()); |
| } |
| |
| void GraphicsContext::strokePath(const Path& pathToStroke) |
| { |
| if (contextDisabled() || pathToStroke.isEmpty()) |
| return; |
| |
| const SkPath& path = pathToStroke.skPath(); |
| drawPath(path, immutableState()->strokePaint()); |
| } |
| |
| void GraphicsContext::strokeRect(const FloatRect& rect) |
| { |
| strokeRect(rect, strokeThickness()); |
| } |
| |
| void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkPaint paint(immutableState()->strokePaint()); |
| paint.setStrokeWidth(WebCoreFloatToSkScalar(lineWidth)); |
| // Reset the dash effect to account for the width |
| immutableState()->strokeData().setupPaintDashPathEffect(&paint, 0); |
| // strokerect has special rules for CSS when the rect is degenerate: |
| // if width==0 && height==0, do nothing |
| // if width==0 || height==0, then just draw line for the other dimension |
| SkRect r(rect); |
| bool validW = r.width() > 0; |
| bool validH = r.height() > 0; |
| if (validW && validH) { |
| drawRect(r, paint); |
| } else if (validW || validH) { |
| // we are expected to respect the lineJoin, so we can't just call |
| // drawLine -- we have to create a path that doubles back on itself. |
| SkPath path; |
| path.moveTo(r.fLeft, r.fTop); |
| path.lineTo(r.fRight, r.fBottom); |
| path.close(); |
| drawPath(path, paint); |
| } |
| } |
| |
| void GraphicsContext::strokeEllipse(const FloatRect& ellipse) |
| { |
| if (contextDisabled()) |
| return; |
| |
| drawOval(ellipse, immutableState()->strokePaint()); |
| } |
| |
| void GraphicsContext::clipRoundedRect(const RoundedRect& rect, SkRegion::Op regionOp) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (!rect.isRounded()) { |
| clipRect(rect.rect(), NotAntiAliased, regionOp); |
| return; |
| } |
| |
| SkVector radii[4]; |
| RoundedRect::Radii wkRadii = rect.radii(); |
| setRadii(radii, wkRadii.topLeft(), wkRadii.topRight(), wkRadii.bottomRight(), wkRadii.bottomLeft()); |
| |
| SkRRect r; |
| r.setRectRadii(rect.rect(), radii); |
| |
| clipRRect(r, AntiAliased, regionOp); |
| } |
| |
| void GraphicsContext::clipOut(const Path& pathToClip) |
| { |
| if (contextDisabled()) |
| return; |
| |
| // Use const_cast and temporarily toggle the inverse fill type instead of copying the path. |
| SkPath& path = const_cast<SkPath&>(pathToClip.skPath()); |
| path.toggleInverseFillType(); |
| clipPath(path, AntiAliased); |
| path.toggleInverseFillType(); |
| } |
| |
| void GraphicsContext::clipPath(const Path& pathToClip, WindRule clipRule) |
| { |
| if (contextDisabled() || pathToClip.isEmpty()) |
| return; |
| |
| // Use const_cast and temporarily modify the fill type instead of copying the path. |
| SkPath& path = const_cast<SkPath&>(pathToClip.skPath()); |
| SkPath::FillType previousFillType = path.getFillType(); |
| |
| SkPath::FillType temporaryFillType = WebCoreWindRuleToSkFillType(clipRule); |
| path.setFillType(temporaryFillType); |
| clipPath(path, AntiAliased); |
| |
| path.setFillType(previousFillType); |
| } |
| |
| void GraphicsContext::clipConvexPolygon(size_t numPoints, const FloatPoint* points, bool antialiased) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (numPoints <= 1) |
| return; |
| |
| SkPath path; |
| setPathFromConvexPoints(&path, numPoints, points); |
| clipPath(path, antialiased ? AntiAliased : NotAntiAliased); |
| } |
| |
| void GraphicsContext::clipOutRoundedRect(const RoundedRect& rect) |
| { |
| if (contextDisabled()) |
| return; |
| |
| clipRoundedRect(rect, SkRegion::kDifference_Op); |
| } |
| |
| void GraphicsContext::canvasClip(const Path& pathToClip, WindRule clipRule) |
| { |
| if (contextDisabled()) |
| return; |
| |
| // Use const_cast and temporarily modify the fill type instead of copying the path. |
| SkPath& path = const_cast<SkPath&>(pathToClip.skPath()); |
| SkPath::FillType previousFillType = path.getFillType(); |
| |
| SkPath::FillType temporaryFillType = WebCoreWindRuleToSkFillType(clipRule); |
| path.setFillType(temporaryFillType); |
| clipPath(path); |
| |
| path.setFillType(previousFillType); |
| } |
| |
| void GraphicsContext::clipRect(const SkRect& rect, AntiAliasingMode aa, SkRegion::Op op) |
| { |
| if (contextDisabled()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->clipRect(rect, op, aa == AntiAliased); |
| } |
| |
| void GraphicsContext::clipPath(const SkPath& path, AntiAliasingMode aa, SkRegion::Op op) |
| { |
| if (contextDisabled()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->clipPath(path, op, aa == AntiAliased); |
| } |
| |
| void GraphicsContext::clipRRect(const SkRRect& rect, AntiAliasingMode aa, SkRegion::Op op) |
| { |
| if (contextDisabled()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->clipRRect(rect, op, aa == AntiAliased); |
| } |
| |
| void GraphicsContext::rotate(float angleInRadians) |
| { |
| if (contextDisabled()) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->rotate(WebCoreFloatToSkScalar(angleInRadians * (180.0f / 3.14159265f))); |
| } |
| |
| void GraphicsContext::translate(float x, float y) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (!x && !y) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->translate(WebCoreFloatToSkScalar(x), WebCoreFloatToSkScalar(y)); |
| } |
| |
| void GraphicsContext::scale(float x, float y) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (x == 1.0f && y == 1.0f) |
| return; |
| |
| realizeCanvasSave(); |
| |
| m_canvas->scale(WebCoreFloatToSkScalar(x), WebCoreFloatToSkScalar(y)); |
| } |
| |
| void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkAutoDataUnref url(SkData::NewWithCString(link.string().utf8().data())); |
| SkAnnotateRectWithURL(m_canvas, destRect, url.get()); |
| } |
| |
| void GraphicsContext::setURLFragmentForRect(const String& destName, const IntRect& rect) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkAutoDataUnref skDestName(SkData::NewWithCString(destName.utf8().data())); |
| SkAnnotateLinkToDestination(m_canvas, rect, skDestName.get()); |
| } |
| |
| void GraphicsContext::addURLTargetAtPoint(const String& name, const IntPoint& pos) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkAutoDataUnref nameData(SkData::NewWithCString(name.utf8().data())); |
| SkAnnotateNamedDestination(m_canvas, SkPoint::Make(pos.x(), pos.y()), nameData); |
| } |
| |
| AffineTransform GraphicsContext::getCTM() const |
| { |
| if (contextDisabled()) |
| return AffineTransform(); |
| |
| SkMatrix m = getTotalMatrix(); |
| return AffineTransform(SkScalarToDouble(m.getScaleX()), |
| SkScalarToDouble(m.getSkewY()), |
| SkScalarToDouble(m.getSkewX()), |
| SkScalarToDouble(m.getScaleY()), |
| SkScalarToDouble(m.getTranslateX()), |
| SkScalarToDouble(m.getTranslateY())); |
| } |
| |
| void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, CompositeOperator op) |
| { |
| if (contextDisabled()) |
| return; |
| |
| CompositeOperator previousOperator = compositeOperation(); |
| setCompositeOperation(op); |
| fillRect(rect, color); |
| setCompositeOperation(previousOperator); |
| } |
| |
| void GraphicsContext::fillRoundedRect(const RoundedRect& rect, const Color& color) |
| { |
| if (contextDisabled()) |
| return; |
| |
| if (rect.isRounded()) |
| fillRoundedRect(rect.rect(), rect.radii().topLeft(), rect.radii().topRight(), rect.radii().bottomLeft(), rect.radii().bottomRight(), color); |
| else |
| fillRect(rect.rect(), color); |
| } |
| |
| void GraphicsContext::fillRectWithRoundedHole(const IntRect& rect, const RoundedRect& roundedHoleRect, const Color& color) |
| { |
| if (contextDisabled()) |
| return; |
| |
| Path path; |
| path.addRect(rect); |
| |
| if (!roundedHoleRect.radii().isZero()) |
| path.addRoundedRect(roundedHoleRect); |
| else |
| path.addRect(roundedHoleRect.rect()); |
| |
| WindRule oldFillRule = fillRule(); |
| Color oldFillColor = fillColor(); |
| |
| setFillRule(RULE_EVENODD); |
| setFillColor(color); |
| |
| fillPath(path); |
| |
| setFillRule(oldFillRule); |
| setFillColor(oldFillColor); |
| } |
| |
| void GraphicsContext::clearRect(const FloatRect& rect) |
| { |
| if (contextDisabled()) |
| return; |
| |
| SkRect r = rect; |
| SkPaint paint(immutableState()->fillPaint()); |
| paint.setXfermodeMode(SkXfermode::kClear_Mode); |
| drawRect(r, paint); |
| } |
| |
| void GraphicsContext::adjustLineToPixelBoundaries(FloatPoint& p1, FloatPoint& p2, float strokeWidth, StrokeStyle penStyle) |
| { |
| // For odd widths, we add in 0.5 to the appropriate x/y so that the float arithmetic |
| // works out. For example, with a border width of 3, WebKit will pass us (y1+y2)/2, e.g., |
| // (50+53)/2 = 103/2 = 51 when we want 51.5. It is always true that an even width gave |
| // us a perfect position, but an odd width gave us a position that is off by exactly 0.5. |
| if (penStyle == DottedStroke || penStyle == DashedStroke) { |
| if (p1.x() == p2.x()) { |
| p1.setY(p1.y() + strokeWidth); |
| p2.setY(p2.y() - strokeWidth); |
| } else { |
| p1.setX(p1.x() + strokeWidth); |
| p2.setX(p2.x() - strokeWidth); |
| } |
| } |
| |
| if (static_cast<int>(strokeWidth) % 2) { //odd |
| if (p1.x() == p2.x()) { |
| // We're a vertical line. Adjust our x. |
| p1.setX(p1.x() + 0.5f); |
| p2.setX(p2.x() + 0.5f); |
| } else { |
| // We're a horizontal line. Adjust our y. |
| p1.setY(p1.y() + 0.5f); |
| p2.setY(p2.y() + 0.5f); |
| } |
| } |
| } |
| |
| PassOwnPtr<ImageBuffer> GraphicsContext::createRasterBuffer(const IntSize& size, OpacityMode opacityMode) const |
| { |
| // Make the buffer larger if the context's transform is scaling it so we need a higher |
| // resolution than one pixel per unit. Also set up a corresponding scale factor on the |
| // graphics context. |
| |
| AffineTransform transform = getCTM(); |
| IntSize scaledSize(static_cast<int>(ceil(size.width() * transform.xScale())), static_cast<int>(ceil(size.height() * transform.yScale()))); |
| |
| SkAlphaType alphaType = (opacityMode == Opaque) ? kOpaque_SkAlphaType : kPremul_SkAlphaType; |
| SkImageInfo info = SkImageInfo::MakeN32(size.width(), size.height(), alphaType); |
| RefPtr<SkSurface> skSurface = adoptRef(SkSurface::NewRaster(info)); |
| if (!skSurface) |
| return nullptr; |
| OwnPtr<ImageBufferSurface> surface = adoptPtr(new CompatibleImageBufferSurface(skSurface.release(), scaledSize, opacityMode)); |
| ASSERT(surface->isValid()); |
| OwnPtr<ImageBuffer> buffer = adoptPtr(new ImageBuffer(surface.release())); |
| |
| buffer->context()->scale(static_cast<float>(scaledSize.width()) / size.width(), |
| static_cast<float>(scaledSize.height()) / size.height()); |
| |
| return buffer.release(); |
| } |
| |
| void GraphicsContext::setPathFromConvexPoints(SkPath* path, size_t numPoints, const FloatPoint* points) |
| { |
| path->incReserve(numPoints); |
| path->moveTo(WebCoreFloatToSkScalar(points[0].x()), |
| WebCoreFloatToSkScalar(points[0].y())); |
| for (size_t i = 1; i < numPoints; ++i) { |
| path->lineTo(WebCoreFloatToSkScalar(points[i].x()), |
| WebCoreFloatToSkScalar(points[i].y())); |
| } |
| |
| /* The code used to just blindly call this |
| path->setIsConvex(true); |
| But webkit can sometimes send us non-convex 4-point values, so we mark the path's |
| convexity as unknown, so it will get computed by skia at draw time. |
| See crbug.com 108605 |
| */ |
| SkPath::Convexity convexity = SkPath::kConvex_Convexity; |
| if (numPoints == 4) |
| convexity = SkPath::kUnknown_Convexity; |
| path->setConvexity(convexity); |
| } |
| |
| void GraphicsContext::setRadii(SkVector* radii, IntSize topLeft, IntSize topRight, IntSize bottomRight, IntSize bottomLeft) |
| { |
| radii[SkRRect::kUpperLeft_Corner].set(SkIntToScalar(topLeft.width()), |
| SkIntToScalar(topLeft.height())); |
| radii[SkRRect::kUpperRight_Corner].set(SkIntToScalar(topRight.width()), |
| SkIntToScalar(topRight.height())); |
| radii[SkRRect::kLowerRight_Corner].set(SkIntToScalar(bottomRight.width()), |
| SkIntToScalar(bottomRight.height())); |
| radii[SkRRect::kLowerLeft_Corner].set(SkIntToScalar(bottomLeft.width()), |
| SkIntToScalar(bottomLeft.height())); |
| } |
| |
| PassRefPtr<SkColorFilter> GraphicsContext::WebCoreColorFilterToSkiaColorFilter(ColorFilter colorFilter) |
| { |
| switch (colorFilter) { |
| case ColorFilterLuminanceToAlpha: |
| return adoptRef(SkLumaColorFilter::Create()); |
| case ColorFilterLinearRGBToSRGB: |
| return ImageBuffer::createColorSpaceFilter(ColorSpaceLinearRGB, ColorSpaceDeviceRGB); |
| case ColorFilterSRGBToLinearRGB: |
| return ImageBuffer::createColorSpaceFilter(ColorSpaceDeviceRGB, ColorSpaceLinearRGB); |
| case ColorFilterNone: |
| break; |
| default: |
| ASSERT_NOT_REACHED(); |
| break; |
| } |
| |
| return nullptr; |
| } |
| |
| void GraphicsContext::draw2xMarker(SkBitmap* bitmap, int index) |
| { |
| const SkPMColor lineColor = lineColors(index); |
| const SkPMColor antiColor1 = antiColors1(index); |
| const SkPMColor antiColor2 = antiColors2(index); |
| |
| uint32_t* row1 = bitmap->getAddr32(0, 0); |
| uint32_t* row2 = bitmap->getAddr32(0, 1); |
| uint32_t* row3 = bitmap->getAddr32(0, 2); |
| uint32_t* row4 = bitmap->getAddr32(0, 3); |
| |
| // Pattern: X0o o0X0o o0 |
| // XX0o o0XXX0o o0X |
| // o0XXX0o o0XXX0o |
| // o0X0o o0X0o |
| const SkPMColor row1Color[] = { lineColor, antiColor1, antiColor2, 0, 0, 0, antiColor2, antiColor1 }; |
| const SkPMColor row2Color[] = { lineColor, lineColor, antiColor1, antiColor2, 0, antiColor2, antiColor1, lineColor }; |
| const SkPMColor row3Color[] = { 0, antiColor2, antiColor1, lineColor, lineColor, lineColor, antiColor1, antiColor2 }; |
| const SkPMColor row4Color[] = { 0, 0, antiColor2, antiColor1, lineColor, antiColor1, antiColor2, 0 }; |
| |
| for (int x = 0; x < bitmap->width() + 8; x += 8) { |
| int count = std::min(bitmap->width() - x, 8); |
| if (count > 0) { |
| memcpy(row1 + x, row1Color, count * sizeof(SkPMColor)); |
| memcpy(row2 + x, row2Color, count * sizeof(SkPMColor)); |
| memcpy(row3 + x, row3Color, count * sizeof(SkPMColor)); |
| memcpy(row4 + x, row4Color, count * sizeof(SkPMColor)); |
| } |
| } |
| } |
| |
| void GraphicsContext::draw1xMarker(SkBitmap* bitmap, int index) |
| { |
| const uint32_t lineColor = lineColors(index); |
| const uint32_t antiColor = antiColors2(index); |
| |
| // Pattern: X o o X o o X |
| // o X o o X o |
| uint32_t* row1 = bitmap->getAddr32(0, 0); |
| uint32_t* row2 = bitmap->getAddr32(0, 1); |
| for (int x = 0; x < bitmap->width(); x++) { |
| switch (x % 4) { |
| case 0: |
| row1[x] = lineColor; |
| break; |
| case 1: |
| row1[x] = antiColor; |
| row2[x] = antiColor; |
| break; |
| case 2: |
| row2[x] = lineColor; |
| break; |
| case 3: |
| row1[x] = antiColor; |
| row2[x] = antiColor; |
| break; |
| } |
| } |
| } |
| |
| SkPMColor GraphicsContext::lineColors(int index) |
| { |
| static const SkPMColor colors[] = { |
| SkPreMultiplyARGB(0xFF, 0xFF, 0x00, 0x00), // Opaque red. |
| SkPreMultiplyARGB(0xFF, 0xC0, 0xC0, 0xC0) // Opaque gray. |
| }; |
| |
| return colors[index]; |
| } |
| |
| SkPMColor GraphicsContext::antiColors1(int index) |
| { |
| static const SkPMColor colors[] = { |
| SkPreMultiplyARGB(0xB0, 0xFF, 0x00, 0x00), // Semitransparent red. |
| SkPreMultiplyARGB(0xB0, 0xC0, 0xC0, 0xC0) // Semitransparent gray. |
| }; |
| |
| return colors[index]; |
| } |
| |
| SkPMColor GraphicsContext::antiColors2(int index) |
| { |
| static const SkPMColor colors[] = { |
| SkPreMultiplyARGB(0x60, 0xFF, 0x00, 0x00), // More transparent red |
| SkPreMultiplyARGB(0x60, 0xC0, 0xC0, 0xC0) // More transparent gray |
| }; |
| |
| return colors[index]; |
| } |
| |
| void GraphicsContext::didDrawTextInRect(const SkRect& textRect) |
| { |
| if (m_trackTextRegion) { |
| TRACE_EVENT0("skia", "GraphicsContext::didDrawTextInRect"); |
| m_textRegion.join(textRect); |
| } |
| } |
| |
| void GraphicsContext::preparePaintForDrawRectToRect( |
| SkPaint* paint, |
| const SkRect& srcRect, |
| const SkRect& destRect, |
| CompositeOperator compositeOp, |
| WebBlendMode blendMode, |
| bool isLazyDecoded, |
| bool isDataComplete) const |
| { |
| paint->setXfermodeMode(WebCoreCompositeToSkiaComposite(compositeOp, blendMode)); |
| paint->setColorFilter(this->colorFilter()); |
| paint->setAlpha(this->getNormalizedAlpha()); |
| paint->setLooper(this->drawLooper()); |
| paint->setAntiAlias(shouldDrawAntiAliased(this, destRect)); |
| |
| InterpolationQuality resampling; |
| if (this->isAccelerated()) { |
| resampling = InterpolationLow; |
| } else if (isLazyDecoded) { |
| resampling = InterpolationHigh; |
| } else { |
| // Take into account scale applied to the canvas when computing sampling mode (e.g. CSS scale or page scale). |
| SkRect destRectTarget = destRect; |
| SkMatrix totalMatrix = this->getTotalMatrix(); |
| if (!(totalMatrix.getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask))) |
| totalMatrix.mapRect(&destRectTarget, destRect); |
| |
| resampling = computeInterpolationQuality(totalMatrix, |
| SkScalarToFloat(srcRect.width()), SkScalarToFloat(srcRect.height()), |
| SkScalarToFloat(destRectTarget.width()), SkScalarToFloat(destRectTarget.height()), |
| isDataComplete); |
| } |
| |
| if (resampling == InterpolationNone) { |
| // FIXME: This is to not break tests (it results in the filter bitmap flag |
| // being set to true). We need to decide if we respect InterpolationNone |
| // being returned from computeInterpolationQuality. |
| resampling = InterpolationLow; |
| } |
| resampling = limitInterpolationQuality(this, resampling); |
| paint->setFilterLevel(static_cast<SkPaint::FilterLevel>(resampling)); |
| } |
| |
| } // namespace blink |