blob: 877da89a4af8dcf7c4e061c1312eeafdd7715004 [file] [log] [blame]
/*
* Copyright (c) 2006, 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:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "GraphicsContext.h"
#include "Color.h"
#include "FloatRect.h"
#include "Gradient.h"
#include "GraphicsContextPlatformPrivate.h"
#include "GraphicsContextPrivate.h"
#include "ImageBuffer.h"
#include "IntRect.h"
#include "NativeImageSkia.h"
#include "NotImplemented.h"
#include "PlatformContextSkia.h"
#include "TransformationMatrix.h"
#include "SkBitmap.h"
#include "SkBlurDrawLooper.h"
#include "SkCornerPathEffect.h"
#include "SkShader.h"
#include "SkiaUtils.h"
#include "skia/ext/platform_canvas.h"
#include <math.h>
#include <wtf/Assertions.h>
#include <wtf/MathExtras.h>
using namespace std;
namespace WebCore {
namespace {
inline int fastMod(int value, int max)
{
int sign = SkExtractSign(value);
value = SkApplySign(value, sign);
if (value >= max)
value %= max;
return SkApplySign(value, sign);
}
inline float square(float n)
{
return n * n;
}
} // namespace
// "Seatbelt" functions ------------------------------------------------------
//
// These functions check certain graphics primitives for being "safe".
// Skia has historically crashed when sent crazy data. These functions do
// additional checking to prevent crashes.
//
// Ideally, all of these would be fixed in the graphics layer and we would not
// have to do any checking. You can uncomment the ENSURE_VALUE_SAFETY_FOR_SKIA
// flag to check the graphics layer.
#define ENSURE_VALUE_SAFETY_FOR_SKIA
static bool isCoordinateSkiaSafe(float coord)
{
#ifdef ENSURE_VALUE_SAFETY_FOR_SKIA
// First check for valid floats.
#if defined(_MSC_VER)
if (!_finite(coord))
#else
if (!finite(coord))
#endif
return false;
// Skia uses 16.16 fixed point and 26.6 fixed point in various places. If
// the transformed point exceeds 15 bits, we just declare that it's
// unreasonable to catch both of these cases.
static const int maxPointMagnitude = 32767;
if (coord > maxPointMagnitude || coord < -maxPointMagnitude)
return false;
return true;
#else
return true;
#endif
}
static bool isPointSkiaSafe(const SkMatrix& transform, const SkPoint& pt)
{
#ifdef ENSURE_VALUE_SAFETY_FOR_SKIA
// Now check for points that will overflow. We check the *transformed*
// points since this is what will be rasterized.
SkPoint xPt;
transform.mapPoints(&xPt, &pt, 1);
return isCoordinateSkiaSafe(xPt.fX) && isCoordinateSkiaSafe(xPt.fY);
#else
return true;
#endif
}
static bool isRectSkiaSafe(const SkMatrix& transform, const SkRect& rc)
{
#ifdef ENSURE_VALUE_SAFETY_FOR_SKIA
SkPoint topleft = {rc.fLeft, rc.fTop};
SkPoint bottomright = {rc.fRight, rc.fBottom};
return isPointSkiaSafe(transform, topleft) && isPointSkiaSafe(transform, bottomright);
#else
return true;
#endif
}
bool isPathSkiaSafe(const SkMatrix& transform, const SkPath& path)
{
#ifdef ENSURE_VALUE_SAFETY_FOR_SKIA
SkPoint current_points[4];
SkPath::Iter iter(path, false);
for (SkPath::Verb verb = iter.next(current_points);
verb != SkPath::kDone_Verb;
verb = iter.next(current_points)) {
switch (verb) {
case SkPath::kMove_Verb:
// This move will be duplicated in the next verb, so we can ignore.
break;
case SkPath::kLine_Verb:
// iter.next returns 2 points.
if (!isPointSkiaSafe(transform, current_points[0])
|| !isPointSkiaSafe(transform, current_points[1]))
return false;
break;
case SkPath::kQuad_Verb:
// iter.next returns 3 points.
if (!isPointSkiaSafe(transform, current_points[0])
|| !isPointSkiaSafe(transform, current_points[1])
|| !isPointSkiaSafe(transform, current_points[2]))
return false;
break;
case SkPath::kCubic_Verb:
// iter.next returns 4 points.
if (!isPointSkiaSafe(transform, current_points[0])
|| !isPointSkiaSafe(transform, current_points[1])
|| !isPointSkiaSafe(transform, current_points[2])
|| !isPointSkiaSafe(transform, current_points[3]))
return false;
break;
case SkPath::kClose_Verb:
case SkPath::kDone_Verb:
default:
break;
}
}
return true;
#else
return true;
#endif
}
// Local helper functions ------------------------------------------------------
void addCornerArc(SkPath* path, const SkRect& rect, const IntSize& size, int startAngle)
{
SkIRect ir;
int rx = SkMin32(SkScalarRound(rect.width()), size.width());
int ry = SkMin32(SkScalarRound(rect.height()), size.height());
ir.set(-rx, -ry, rx, ry);
switch (startAngle) {
case 0:
ir.offset(rect.fRight - ir.fRight, rect.fBottom - ir.fBottom);
break;
case 90:
ir.offset(rect.fLeft - ir.fLeft, rect.fBottom - ir.fBottom);
break;
case 180:
ir.offset(rect.fLeft - ir.fLeft, rect.fTop - ir.fTop);
break;
case 270:
ir.offset(rect.fRight - ir.fRight, rect.fTop - ir.fTop);
break;
default:
ASSERT(0);
}
SkRect r;
r.set(ir);
path->arcTo(r, SkIntToScalar(startAngle), SkIntToScalar(90), false);
}
// -----------------------------------------------------------------------------
// This may be called with a NULL pointer to create a graphics context that has
// no painting.
GraphicsContext::GraphicsContext(PlatformGraphicsContext* gc)
: m_common(createGraphicsContextPrivate())
, m_data(new GraphicsContextPlatformPrivate(gc))
{
setPaintingDisabled(!gc || !platformContext()->canvas());
}
GraphicsContext::~GraphicsContext()
{
delete m_data;
this->destroyGraphicsContextPrivate(m_common);
}
PlatformGraphicsContext* GraphicsContext::platformContext() const
{
ASSERT(!paintingDisabled());
return m_data->context();
}
// State saving ----------------------------------------------------------------
void GraphicsContext::savePlatformState()
{
if (paintingDisabled())
return;
// Save our private State.
platformContext()->save();
}
void GraphicsContext::restorePlatformState()
{
if (paintingDisabled())
return;
// Restore our private State.
platformContext()->restore();
}
void GraphicsContext::beginTransparencyLayer(float opacity)
{
if (paintingDisabled())
return;
// We need the "alpha" layer flag here because the base layer is opaque
// (the surface of the page) but layers on top may have transparent parts.
// Without explicitly setting the alpha flag, the layer will inherit the
// opaque setting of the base and some things won't work properly.
platformContext()->canvas()->saveLayerAlpha(
0,
static_cast<unsigned char>(opacity * 255),
static_cast<SkCanvas::SaveFlags>(SkCanvas::kHasAlphaLayer_SaveFlag |
SkCanvas::kFullColorLayer_SaveFlag));
}
void GraphicsContext::endTransparencyLayer()
{
if (paintingDisabled())
return;
platformContext()->canvas()->restore();
}
// Graphics primitives ---------------------------------------------------------
void GraphicsContext::addInnerRoundedRectClip(const IntRect& rect, int thickness)
{
if (paintingDisabled())
return;
SkRect r(rect);
if (!isRectSkiaSafe(getCTM(), r))
return;
SkPath path;
path.addOval(r, SkPath::kCW_Direction);
// only perform the inset if we won't invert r
if (2 * thickness < rect.width() && 2 * thickness < rect.height()) {
// Adding one to the thickness doesn't make the border too thick as
// it's painted over afterwards. But without this adjustment the
// border appears a little anemic after anti-aliasing.
r.inset(SkIntToScalar(thickness + 1), SkIntToScalar(thickness + 1));
path.addOval(r, SkPath::kCCW_Direction);
}
platformContext()->clipPathAntiAliased(path);
}
void GraphicsContext::addPath(const Path& path)
{
if (paintingDisabled())
return;
platformContext()->addPath(*path.platformPath());
}
void GraphicsContext::beginPath()
{
if (paintingDisabled())
return;
platformContext()->beginPath();
}
void GraphicsContext::clearPlatformShadow()
{
if (paintingDisabled())
return;
platformContext()->setDrawLooper(0);
}
void GraphicsContext::clearRect(const FloatRect& rect)
{
if (paintingDisabled())
return;
SkRect r = rect;
if (!isRectSkiaSafe(getCTM(), r))
ClipRectToCanvas(*platformContext()->canvas(), r, &r);
SkPaint paint;
platformContext()->setupPaintForFilling(&paint);
paint.setXfermodeMode(SkXfermode::kClear_Mode);
platformContext()->canvas()->drawRect(r, paint);
}
void GraphicsContext::clip(const FloatRect& rect)
{
if (paintingDisabled())
return;
SkRect r(rect);
if (!isRectSkiaSafe(getCTM(), r))
return;
platformContext()->canvas()->clipRect(r);
}
void GraphicsContext::clip(const Path& path)
{
if (paintingDisabled())
return;
const SkPath& p = *path.platformPath();
if (!isPathSkiaSafe(getCTM(), p))
return;
platformContext()->clipPathAntiAliased(p);
}
void GraphicsContext::canvasClip(const Path& path)
{
if (paintingDisabled())
return;
const SkPath& p = *path.platformPath();
if (!isPathSkiaSafe(getCTM(), p))
return;
platformContext()->canvas()->clipPath(p);
}
void GraphicsContext::clipOut(const IntRect& rect)
{
if (paintingDisabled())
return;
SkRect r(rect);
if (!isRectSkiaSafe(getCTM(), r))
return;
platformContext()->canvas()->clipRect(r, SkRegion::kDifference_Op);
}
void GraphicsContext::clipOut(const Path& p)
{
if (paintingDisabled())
return;
const SkPath& path = *p.platformPath();
if (!isPathSkiaSafe(getCTM(), path))
return;
platformContext()->canvas()->clipPath(path, SkRegion::kDifference_Op);
}
void GraphicsContext::clipOutEllipseInRect(const IntRect& rect)
{
if (paintingDisabled())
return;
SkRect oval(rect);
if (!isRectSkiaSafe(getCTM(), oval))
return;
SkPath path;
path.addOval(oval, SkPath::kCCW_Direction);
platformContext()->canvas()->clipPath(path, SkRegion::kDifference_Op);
}
void GraphicsContext::clipPath(WindRule clipRule)
{
if (paintingDisabled())
return;
SkPath path = platformContext()->currentPathInLocalCoordinates();
if (!isPathSkiaSafe(getCTM(), path))
return;
path.setFillType(clipRule == RULE_EVENODD ? SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType);
platformContext()->clipPathAntiAliased(path);
}
void GraphicsContext::clipToImageBuffer(const FloatRect& rect,
const ImageBuffer* imageBuffer)
{
if (paintingDisabled())
return;
#if defined(__linux__) || PLATFORM(WIN_OS)
platformContext()->beginLayerClippedToImage(rect, imageBuffer);
#endif
}
void GraphicsContext::concatCTM(const TransformationMatrix& xform)
{
if (paintingDisabled())
return;
platformContext()->canvas()->concat(xform);
}
void GraphicsContext::drawConvexPolygon(size_t numPoints,
const FloatPoint* points,
bool shouldAntialias)
{
if (paintingDisabled())
return;
if (numPoints <= 1)
return;
SkPath path;
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()));
}
if (!isPathSkiaSafe(getCTM(), path))
return;
SkPaint paint;
platformContext()->setupPaintForFilling(&paint);
platformContext()->canvas()->drawPath(path, paint);
if (strokeStyle() != NoStroke) {
paint.reset();
platformContext()->setupPaintForStroking(&paint, 0, 0);
platformContext()->canvas()->drawPath(path, paint);
}
}
// This method is only used to draw the little circles used in lists.
void GraphicsContext::drawEllipse(const IntRect& elipseRect)
{
if (paintingDisabled())
return;
SkRect rect = elipseRect;
if (!isRectSkiaSafe(getCTM(), rect))
return;
SkPaint paint;
platformContext()->setupPaintForFilling(&paint);
platformContext()->canvas()->drawOval(rect, paint);
if (strokeStyle() != NoStroke) {
paint.reset();
platformContext()->setupPaintForStroking(&paint, &rect, 0);
platformContext()->canvas()->drawOval(rect, paint);
}
}
void GraphicsContext::drawFocusRing(const Vector<IntRect>& rects, int /* width */, int /* offset */, const Color& color)
{
if (paintingDisabled())
return;
unsigned rectCount = rects.size();
if (!rectCount)
return;
SkRegion focusRingRegion;
const SkScalar focusRingOutset = WebCoreFloatToSkScalar(0.5);
for (unsigned i = 0; i < rectCount; i++) {
SkIRect r = rects[i];
r.inset(-focusRingOutset, -focusRingOutset);
focusRingRegion.op(r, SkRegion::kUnion_Op);
}
SkPath path;
SkPaint paint;
paint.setAntiAlias(true);
paint.setStyle(SkPaint::kStroke_Style);
paint.setColor(color.rgb());
paint.setStrokeWidth(focusRingOutset * 2);
paint.setPathEffect(new SkCornerPathEffect(focusRingOutset * 2))->unref();
focusRingRegion.getBoundaryPath(&path);
platformContext()->canvas()->drawPath(path, paint);
}
// This is only used to draw borders.
void GraphicsContext::drawLine(const IntPoint& point1, const IntPoint& point2)
{
if (paintingDisabled())
return;
StrokeStyle penStyle = strokeStyle();
if (penStyle == NoStroke)
return;
SkPaint paint;
if (!isPointSkiaSafe(getCTM(), point1) || !isPointSkiaSafe(getCTM(), point2))
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 = SkScalarRound(disp.width() + disp.height());
platformContext()->setupPaintForStroking(&paint, 0, 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());
platformContext()->canvas()->drawRect(r1, fillPaint);
platformContext()->canvas()->drawRect(r2, fillPaint);
}
adjustLineToPixelBoundaries(p1, p2, width, penStyle);
SkPoint pts[2] = { (SkPoint)p1, (SkPoint)p2 };
platformContext()->canvas()->drawPoints(SkCanvas::kLines_PointMode, 2, pts, paint);
}
void GraphicsContext::drawLineForMisspellingOrBadGrammar(const IntPoint& pt,
int width,
bool grammar)
{
if (paintingDisabled())
return;
// Create the pattern we'll use to draw the underline.
static SkBitmap* misspellBitmap = 0;
if (!misspellBitmap) {
// 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; // Must be multiple of 4 for pattern below.
const int colPixels = 2;
misspellBitmap = new SkBitmap;
misspellBitmap->setConfig(SkBitmap::kARGB_8888_Config,
rowPixels, colPixels);
misspellBitmap->allocPixels();
misspellBitmap->eraseARGB(0, 0, 0, 0);
const uint32_t lineColor = 0xFFFF0000; // Opaque red.
const uint32_t antiColor = 0x60600000; // Semitransparent red.
// Pattern: X o o X o o X
// o X o o X o
uint32_t* row1 = misspellBitmap->getAddr32(0, 0);
uint32_t* row2 = misspellBitmap->getAddr32(0, 1);
for (int x = 0; x < rowPixels; 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;
}
}
}
// Offset it vertically by 1 so that there's some space under the text.
SkScalar originX = SkIntToScalar(pt.x());
SkScalar originY = SkIntToScalar(pt.y()) + 1;
// Make a shader for the bitmap with an origin of the box we'll draw. This
// shader is refcounted and will have an initial refcount of 1.
SkShader* shader = SkShader::CreateBitmapShader(
*misspellBitmap, SkShader::kRepeat_TileMode,
SkShader::kRepeat_TileMode);
SkMatrix matrix;
matrix.reset();
matrix.postTranslate(originX, originY);
shader->setLocalMatrix(matrix);
// Assign the shader to the paint & release our reference. The paint will
// now own the shader and the shader will be destroyed when the paint goes
// out of scope.
SkPaint paint;
paint.setShader(shader);
shader->unref();
SkRect rect;
rect.set(originX,
originY,
originX + SkIntToScalar(width),
originY + SkIntToScalar(misspellBitmap->height()));
platformContext()->canvas()->drawRect(rect, paint);
}
void GraphicsContext::drawLineForText(const IntPoint& pt,
int width,
bool printing)
{
if (paintingDisabled())
return;
if (width <= 0)
return;
int thickness = SkMax32(static_cast<int>(strokeThickness()), 1);
SkRect r;
r.fLeft = SkIntToScalar(pt.x());
r.fTop = SkIntToScalar(pt.y());
r.fRight = r.fLeft + SkIntToScalar(width);
r.fBottom = r.fTop + SkIntToScalar(thickness);
SkPaint paint;
platformContext()->setupPaintForFilling(&paint);
// Text lines are drawn using the stroke color.
paint.setColor(platformContext()->effectiveStrokeColor());
platformContext()->canvas()->drawRect(r, paint);
}
// Draws a filled rectangle with a stroked border.
void GraphicsContext::drawRect(const IntRect& rect)
{
if (paintingDisabled())
return;
SkRect r = rect;
if (!isRectSkiaSafe(getCTM(), r)) {
// See the fillRect below.
ClipRectToCanvas(*platformContext()->canvas(), r, &r);
}
platformContext()->drawRect(r);
}
void GraphicsContext::fillPath()
{
if (paintingDisabled())
return;
SkPath path = platformContext()->currentPathInLocalCoordinates();
if (!isPathSkiaSafe(getCTM(), path))
return;
const GraphicsContextState& state = m_common->state;
path.setFillType(state.fillRule == RULE_EVENODD ?
SkPath::kEvenOdd_FillType : SkPath::kWinding_FillType);
SkPaint paint;
platformContext()->setupPaintForFilling(&paint);
platformContext()->canvas()->drawPath(path, paint);
}
void GraphicsContext::fillRect(const FloatRect& rect)
{
if (paintingDisabled())
return;
SkRect r = rect;
if (!isRectSkiaSafe(getCTM(), r)) {
// See the other version of fillRect below.
ClipRectToCanvas(*platformContext()->canvas(), r, &r);
}
const GraphicsContextState& state = m_common->state;
SkPaint paint;
platformContext()->setupPaintForFilling(&paint);
platformContext()->canvas()->drawRect(r, paint);
}
void GraphicsContext::fillRect(const FloatRect& rect, const Color& color, ColorSpace colorSpace)
{
if (paintingDisabled())
return;
SkRect r = rect;
if (!isRectSkiaSafe(getCTM(), r)) {
// Special case when the rectangle overflows fixed point. This is a
// workaround to fix bug 1212844. When the input rectangle is very
// large, it can overflow Skia's internal fixed point rect. This
// should be fixable in Skia (since the output bitmap isn't that
// large), but until that is fixed, we try to handle it ourselves.
//
// We manually clip the rectangle to the current clip rect. This
// will prevent overflow. The rectangle will be transformed to the
// canvas' coordinate space before it is converted to fixed point
// so we are guaranteed not to overflow after doing this.
ClipRectToCanvas(*platformContext()->canvas(), r, &r);
}
SkPaint paint;
platformContext()->setupPaintCommon(&paint);
paint.setColor(color.rgb());
platformContext()->canvas()->drawRect(r, paint);
}
void GraphicsContext::fillRoundedRect(const IntRect& rect,
const IntSize& topLeft,
const IntSize& topRight,
const IntSize& bottomLeft,
const IntSize& bottomRight,
const Color& color,
ColorSpace colorSpace)
{
if (paintingDisabled())
return;
SkRect r = rect;
if (!isRectSkiaSafe(getCTM(), r))
// See fillRect().
ClipRectToCanvas(*platformContext()->canvas(), r, &r);
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, colorSpace);
return;
}
SkPath path;
addCornerArc(&path, r, topRight, 270);
addCornerArc(&path, r, bottomRight, 0);
addCornerArc(&path, r, bottomLeft, 90);
addCornerArc(&path, r, topLeft, 180);
SkPaint paint;
platformContext()->setupPaintForFilling(&paint);
platformContext()->canvas()->drawPath(path, paint);
}
TransformationMatrix GraphicsContext::getCTM() const
{
const SkMatrix& m = platformContext()->canvas()->getTotalMatrix();
return TransformationMatrix(SkScalarToDouble(m.getScaleX()), // a
SkScalarToDouble(m.getSkewY()), // b
SkScalarToDouble(m.getSkewX()), // c
SkScalarToDouble(m.getScaleY()), // d
SkScalarToDouble(m.getTranslateX()), // e
SkScalarToDouble(m.getTranslateY())); // f
}
FloatRect GraphicsContext::roundToDevicePixels(const FloatRect& rect)
{
// This logic is copied from GraphicsContextCG, eseidel 5/05/08
// It is not enough just to round to pixels in device space. The rotation
// part of the affine transform matrix to device space can mess with this
// conversion if we have a rotating image like the hands of the world clock
// widget. We just need the scale, so we get the affine transform matrix and
// extract the scale.
const SkMatrix& deviceMatrix = platformContext()->canvas()->getTotalMatrix();
if (deviceMatrix.isIdentity())
return rect;
float deviceScaleX = sqrtf(square(deviceMatrix.getScaleX())
+ square(deviceMatrix.getSkewY()));
float deviceScaleY = sqrtf(square(deviceMatrix.getSkewX())
+ square(deviceMatrix.getScaleY()));
FloatPoint deviceOrigin(rect.x() * deviceScaleX, rect.y() * deviceScaleY);
FloatPoint deviceLowerRight((rect.x() + rect.width()) * deviceScaleX,
(rect.y() + rect.height()) * deviceScaleY);
deviceOrigin.setX(roundf(deviceOrigin.x()));
deviceOrigin.setY(roundf(deviceOrigin.y()));
deviceLowerRight.setX(roundf(deviceLowerRight.x()));
deviceLowerRight.setY(roundf(deviceLowerRight.y()));
// Don't let the height or width round to 0 unless either was originally 0
if (deviceOrigin.y() == deviceLowerRight.y() && rect.height())
deviceLowerRight.move(0, 1);
if (deviceOrigin.x() == deviceLowerRight.x() && rect.width())
deviceLowerRight.move(1, 0);
FloatPoint roundedOrigin(deviceOrigin.x() / deviceScaleX,
deviceOrigin.y() / deviceScaleY);
FloatPoint roundedLowerRight(deviceLowerRight.x() / deviceScaleX,
deviceLowerRight.y() / deviceScaleY);
return FloatRect(roundedOrigin, roundedLowerRight - roundedOrigin);
}
void GraphicsContext::scale(const FloatSize& size)
{
if (paintingDisabled())
return;
platformContext()->canvas()->scale(WebCoreFloatToSkScalar(size.width()),
WebCoreFloatToSkScalar(size.height()));
}
void GraphicsContext::setAlpha(float alpha)
{
if (paintingDisabled())
return;
platformContext()->setAlpha(alpha);
}
void GraphicsContext::setCompositeOperation(CompositeOperator op)
{
if (paintingDisabled())
return;
platformContext()->setXfermodeMode(WebCoreCompositeToSkiaComposite(op));
}
void GraphicsContext::setImageInterpolationQuality(InterpolationQuality)
{
notImplemented();
}
void GraphicsContext::setLineCap(LineCap cap)
{
if (paintingDisabled())
return;
switch (cap) {
case ButtCap:
platformContext()->setLineCap(SkPaint::kButt_Cap);
break;
case RoundCap:
platformContext()->setLineCap(SkPaint::kRound_Cap);
break;
case SquareCap:
platformContext()->setLineCap(SkPaint::kSquare_Cap);
break;
default:
ASSERT(0);
break;
}
}
void GraphicsContext::setLineDash(const DashArray& dashes, float dashOffset)
{
if (paintingDisabled())
return;
// FIXME: This is lifted directly off SkiaSupport, lines 49-74
// so it is not guaranteed to work correctly.
size_t dashLength = dashes.size();
if (!dashLength) {
// If no dash is set, revert to solid stroke
// FIXME: do we need to set NoStroke in some cases?
platformContext()->setStrokeStyle(SolidStroke);
platformContext()->setDashPathEffect(0);
return;
}
size_t count = !(dashLength % 2) ? dashLength : dashLength * 2;
SkScalar* intervals = new SkScalar[count];
for (unsigned int i = 0; i < count; i++)
intervals[i] = dashes[i % dashLength];
platformContext()->setDashPathEffect(new SkDashPathEffect(intervals, count, dashOffset));
delete[] intervals;
}
void GraphicsContext::setLineJoin(LineJoin join)
{
if (paintingDisabled())
return;
switch (join) {
case MiterJoin:
platformContext()->setLineJoin(SkPaint::kMiter_Join);
break;
case RoundJoin:
platformContext()->setLineJoin(SkPaint::kRound_Join);
break;
case BevelJoin:
platformContext()->setLineJoin(SkPaint::kBevel_Join);
break;
default:
ASSERT(0);
break;
}
}
void GraphicsContext::setMiterLimit(float limit)
{
if (paintingDisabled())
return;
platformContext()->setMiterLimit(limit);
}
void GraphicsContext::setPlatformFillColor(const Color& color, ColorSpace colorSpace)
{
if (paintingDisabled())
return;
platformContext()->setFillColor(color.rgb());
}
void GraphicsContext::setPlatformFillGradient(Gradient* gradient)
{
if (paintingDisabled())
return;
platformContext()->setFillShader(gradient->platformGradient());
}
void GraphicsContext::setPlatformFillPattern(Pattern* pattern)
{
if (paintingDisabled())
return;
SkShader* pat = pattern->createPlatformPattern(getCTM());
platformContext()->setFillShader(pat);
pat->safeUnref();
}
void GraphicsContext::setPlatformShadow(const IntSize& size,
int blurInt,
const Color& color,
ColorSpace colorSpace)
{
if (paintingDisabled())
return;
// Detect when there's no effective shadow and clear the looper.
if (!size.width() && !size.height() && !blurInt) {
platformContext()->setDrawLooper(0);
return;
}
double width = size.width();
double height = size.height();
double blur = blurInt;
// TODO(tc): This still does not address the issue that shadows
// within canvas elements should ignore transforms.
if (m_common->state.shadowsIgnoreTransforms) {
// Currently only the GraphicsContext associated with the
// CanvasRenderingContext for HTMLCanvasElement have shadows ignore
// Transforms. So with this flag set, we know this state is associated
// with a CanvasRenderingContext.
// CG uses natural orientation for Y axis, but the HTML5 canvas spec
// does not.
// So we now flip the height since it was flipped in
// CanvasRenderingContext in order to work with CG.
height = -height;
}
SkColor c;
if (color.isValid())
c = color.rgb();
else
c = SkColorSetARGB(0xFF/3, 0, 0, 0); // "std" apple shadow color.
// TODO(tc): Should we have a max value for the blur? CG clamps at 1000.0
// for perf reasons.
SkDrawLooper* dl = new SkBlurDrawLooper(blur / 2, width, height, c);
platformContext()->setDrawLooper(dl);
dl->unref();
}
void GraphicsContext::setPlatformStrokeColor(const Color& strokecolor, ColorSpace colorSpace)
{
if (paintingDisabled())
return;
platformContext()->setStrokeColor(strokecolor.rgb());
}
void GraphicsContext::setPlatformStrokeStyle(const StrokeStyle& stroke)
{
if (paintingDisabled())
return;
platformContext()->setStrokeStyle(stroke);
}
void GraphicsContext::setPlatformStrokeThickness(float thickness)
{
if (paintingDisabled())
return;
platformContext()->setStrokeThickness(thickness);
}
void GraphicsContext::setPlatformStrokeGradient(Gradient* gradient)
{
if (paintingDisabled())
return;
platformContext()->setStrokeShader(gradient->platformGradient());
}
void GraphicsContext::setPlatformStrokePattern(Pattern* pattern)
{
if (paintingDisabled())
return;
SkShader* pat = pattern->createPlatformPattern(getCTM());
platformContext()->setStrokeShader(pat);
pat->safeUnref();
}
void GraphicsContext::setPlatformTextDrawingMode(int mode)
{
if (paintingDisabled())
return;
platformContext()->setTextDrawingMode(mode);
}
void GraphicsContext::setURLForRect(const KURL& link, const IntRect& destRect)
{
}
void GraphicsContext::setPlatformShouldAntialias(bool enable)
{
if (paintingDisabled())
return;
platformContext()->setUseAntialiasing(enable);
}
void GraphicsContext::strokeArc(const IntRect& r, int startAngle, int angleSpan)
{
if (paintingDisabled())
return;
SkPaint paint;
SkRect oval = r;
if (strokeStyle() == NoStroke) {
// Stroke using the fill color.
// TODO(brettw) is this really correct? It seems unreasonable.
platformContext()->setupPaintForFilling(&paint);
paint.setStyle(SkPaint::kStroke_Style);
paint.setStrokeWidth(WebCoreFloatToSkScalar(strokeThickness()));
} else
platformContext()->setupPaintForStroking(&paint, 0, 0);
// We do this before converting to scalar, so we don't overflow SkFixed.
startAngle = fastMod(startAngle, 360);
angleSpan = fastMod(angleSpan, 360);
SkPath path;
path.addArc(oval, SkIntToScalar(-startAngle), SkIntToScalar(-angleSpan));
if (!isPathSkiaSafe(getCTM(), path))
return;
platformContext()->canvas()->drawPath(path, paint);
}
void GraphicsContext::strokePath()
{
if (paintingDisabled())
return;
SkPath path = platformContext()->currentPathInLocalCoordinates();
if (!isPathSkiaSafe(getCTM(), path))
return;
const GraphicsContextState& state = m_common->state;
SkPaint paint;
platformContext()->setupPaintForStroking(&paint, 0, 0);
platformContext()->canvas()->drawPath(path, paint);
}
void GraphicsContext::strokeRect(const FloatRect& rect, float lineWidth)
{
if (paintingDisabled())
return;
if (!isRectSkiaSafe(getCTM(), rect))
return;
const GraphicsContextState& state = m_common->state;
SkPaint paint;
platformContext()->setupPaintForStroking(&paint, 0, 0);
paint.setStrokeWidth(WebCoreFloatToSkScalar(lineWidth));
platformContext()->canvas()->drawRect(rect, paint);
}
void GraphicsContext::rotate(float angleInRadians)
{
if (paintingDisabled())
return;
platformContext()->canvas()->rotate(WebCoreFloatToSkScalar(
angleInRadians * (180.0f / 3.14159265f)));
}
void GraphicsContext::translate(float w, float h)
{
if (paintingDisabled())
return;
platformContext()->canvas()->translate(WebCoreFloatToSkScalar(w),
WebCoreFloatToSkScalar(h));
}
} // namespace WebCore