flutter_flutter/engine/platform/graphics/GraphicsContext.cpp
Eric Seidel e0fd75b5ab Make absolute and sort all Sky headers
This caused us to lose our gn check certification. :(

Turns out gn check was just ignoring all the header
paths it didn't understand and so gn check passing
for sky wasn't meaning much.  I tried to straighten
out some of the mess in this CL, but its going to take
several more rounds of massaging before gn check
passes again.  On the bright side (almost) all of
our headers are absolute now.  Turns out my script
(attached to the bug) didn't notice ../ includes
but I'll fix that in the next patch.

R=abarth@chromium.org
BUG=435361

Review URL: https://codereview.chromium.org/746023002
2014-11-20 17:42:05 -08:00

1876 lines
59 KiB
C++

/*
* 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::beginCull(const FloatRect& rect)
{
if (contextDisabled())
return;
realizeCanvasSave();
m_canvas->pushCull(rect);
}
void GraphicsContext::endCull()
{
if (contextDisabled())
return;
realizeCanvasSave();
m_canvas->popCull();
}
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