Canvas的drawLine方法用于画线,参数一起始点的x轴位置,参数二起始点的y轴位置,参数三终点的x轴水平位置,参数四y轴垂直位置,最后一个参数为Paint 画刷对象,代码如下:
//frameworks/base/graphics/java/android/graphic/Canvas.java
public class Canvas extends BaseCanvas {
public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint) {
super.drawLine(startX, startY, stopX, stopY, paint);
}
}
调用Canvas的父类BaseCanvas的drawLine方法:
//frameworks/base/graphics/java/android/graphic/BaseCanvas.java
public abstract class BaseCanvas {}
public void drawLine(float startX, float startY, float stopX, float stopY,
@NonNull Paint paint) {
throwIfHasHwFeaturesInSwMode(paint);
nDrawLine(mNativeCanvasWrapper, startX, startY, stopX, stopY, paint.getNativeInstance());
}
}
drawLine
调用nDrawLine方法,nDrawLine方法是Native方法,在android_graphics_Canvas.cpp中实现,下面分别进行分析:
//framework/base/libs/hwui/jni/android_graphics_Canvas.cpp
static void drawLine(JNIEnv* env, jobject, jlong canvasHandle, jfloat startX, jfloat startY,
jfloat stopX, jfloat stopY, jlong paintHandle) {
Paint* paint = reinterpret_cast<Paint*>(paintHandle);
get_canvas(canvasHandle)->drawLine(startX, startY, stopX, stopY, *paint);
}
调用get_canvas取得Canvas:
//framework/base/libs/hwui/jni/android_graphics_Canvas.cpp
static Canvas* get_canvas(jlong canvasHandle) {
return reinterpret_cast<Canvas*>(canvasHandle);
}
SkiaCanvas drawLine
SkiaCanvas继承于Canvas,因此调用SkiaCanvas的drawLine方法,SkiaCanvas的drawLine功能是用于在画布上绘制直线的方法。通过传入两个点的坐标,可以绘制出一条连接这两个点的直线。在绘制直线之前,可以设置线条的粗细和颜色等属性
//frameworks/base/libs/hwui/SkiaCanvas.cpp
class SkiaCanvas : public Canvas {
SkCanvas* mCanvas;
void SkiaCanvas::drawLine(float startX, float startY, float stopX, float stopY,
const Paint& paint) {
applyLooper(&paint,
[&](const SkPaint& p) { mCanvas->drawLine(startX, startY, stopX, stopY, p); });
}
}
SkCanvas drawLine
调用mCanvas(SkCanvas)的drawLine方法:
//external/skia/include/core/SkCanvas.cpp
class SK_API SkCanvas {
void SkCanvas::drawLine(SkScalar x0, SkScalar y0, SkScalar x1, SkScalar y1, const SkPaint& paint) {
SkPoint pts[2];
pts[0].set(x0, y0);
pts[1].set(x1, y1);
this->drawPoints(kLines_PointMode, 2, pts, paint);
}
}
调用SkCanvas的drawPoints方法:
//external/skia/include/core/SkCanvas.cpp
class SK_API SkCanvas {
void SkCanvas::drawPoints(PointMode mode, size_t count, const SkPoint pts[], const SkPaint& paint) {
TRACE_EVENT0("skia", TRACE_FUNC);
this->onDrawPoints(mode, count, pts, paint);
}
}
调用SkCanvas的onDrawPoints方法:
//external/skia/include/core/SkCanvas.cpp
class SK_API SkCanvas {
void SkCanvas::onDrawPoints(PointMode mode, size_t count, const SkPoint pts[],
const SkPaint& paint) {
if ((long)count <= 0 || paint.nothingToDraw()) {
return;
}
SkASSERT(pts != nullptr);
SkRect bounds;
// Compute bounds from points (common for drawing a single line)
if (count == 2) {
bounds.set(pts[0], pts[1]);
} else {
bounds.setBounds(pts, SkToInt(count));
}
// Enforce paint style matches implicit behavior of drawPoints
SkPaint strokePaint = paint;
strokePaint.setStyle(SkPaint::kStroke_Style);
if (this->internalQuickReject(bounds, strokePaint)) {
return;
}
auto layer = this->aboutToDraw(this, strokePaint, &bounds);
if (layer) {
this->topDevice()->drawPoints(mode, count, pts, layer->paint());
}
}
}
调用SkCanvas的topDevice方法,返回SkBaseDevice对象:
//external/skia/include/core/SkCanvas.cpp
SkBaseDevice* SkCanvas::topDevice() const {
SkASSERT(fMCRec->fDevice);
return fMCRec->fDevice;
}
SkBitmapDevice drawPoints
调用SkBaseDevice的drawPoints,由SkBaseDevice的子类SkBitmapDevice实现drawPoints方法:
//external/skia/src/core/SkBitmapDevice.cpp
class SkBitmapDevice : public SkBaseDevice {
void SkBitmapDevice::drawPoints(SkCanvas::PointMode mode, size_t count,
const SkPoint pts[], const SkPaint& paint) {
LOOP_TILER( drawPoints(mode, count, pts, paint, nullptr), nullptr)
}
}
SkDraw drawPoints
调用SkDraw的drawPoints方法:
//external/skia/src/core/SkDraw.cpp
class SkDraw : public SkGlyphRunListPainter::BitmapDevicePainter {
void SkDraw::drawPoints(SkCanvas::PointMode mode, size_t count,
const SkPoint pts[], const SkPaint& paint,
SkBaseDevice* device) const {
// if we're in lines mode, force count to be even
if (SkCanvas::kLines_PointMode == mode) {
count &= ~(size_t)1;
}
if ((long)count <= 0) {
return;
}
SkASSERT(pts != nullptr);
SkDEBUGCODE(this->validate();)
// nothing to draw
if (fRC->isEmpty()) {
return;
}
if (!SkScalarsAreFinite(&pts[0].fX, count * 2)) {
return;
}
SkMatrix ctm = fMatrixProvider->localToDevice();
PtProcRec rec;
if (!device && rec.init(mode, paint, &ctm, fRC)) {
SkAutoBlitterChoose blitter(*this, nullptr, paint);
SkPoint devPts[MAX_DEV_PTS];
SkBlitter* bltr = blitter.get();
PtProcRec::Proc proc = rec.chooseProc(&bltr);
// we have to back up subsequent passes if we're in polygon mode
const size_t backup = (SkCanvas::kPolygon_PointMode == mode);
do {
int n = SkToInt(count);
if (n > MAX_DEV_PTS) {
n = MAX_DEV_PTS;
}
ctm.mapPoints(devPts, pts, n);
if (!SkScalarsAreFinite(&devPts[0].fX, n * 2)) {
return;
}
proc(rec, devPts, n, bltr);
pts += n - backup;
SkASSERT(SkToInt(count) >= n);
count -= n;
if (count > 0) {
count += backup;
}
} while (count != 0);
} else {
switch (mode) {
case SkCanvas::kPoints_PointMode: {
// temporarily mark the paint as filling.
SkPaint newPaint(paint);
newPaint.setStyle(SkPaint::kFill_Style);
SkScalar width = newPaint.getStrokeWidth();
SkScalar radius = SkScalarHalf(width);
if (newPaint.getStrokeCap() == SkPaint::kRound_Cap) {
if (device) {
for (size_t i = 0; i < count; ++i) {
SkRect r = SkRect::MakeLTRB(pts[i].fX - radius, pts[i].fY - radius,
pts[i].fX + radius, pts[i].fY + radius);
device->drawOval(r, newPaint);
}
} else {
SkPath path;
SkMatrix preMatrix;
path.addCircle(0, 0, radius);
for (size_t i = 0; i < count; i++) {
preMatrix.setTranslate(pts[i].fX, pts[i].fY);
// pass true for the last point, since we can modify
// then path then
path.setIsVolatile((count-1) == i);
this->drawPath(path, newPaint, &preMatrix, (count-1) == i);
}
}
} else {
SkRect r;
for (size_t i = 0; i < count; i++) {
r.fLeft = pts[i].fX - radius;
r.fTop = pts[i].fY - radius;
r.fRight = r.fLeft + width;
r.fBottom = r.fTop + width;
if (device) {
device->drawRect(r, newPaint);
} else {
this->drawRect(r, newPaint);
}
}
}
break;
}
case SkCanvas::kLines_PointMode:
if (2 == count && paint.getPathEffect()) {
// most likely a dashed line - see if it is one of the ones
// we can accelerate
SkStrokeRec stroke(paint);
SkPathEffectBase::PointData pointData;
SkPath path = SkPath::Line(pts[0], pts[1]);
SkRect cullRect = SkRect::Make(fRC->getBounds());
if (as_PEB(paint.getPathEffect())->asPoints(&pointData, path, stroke, ctm,
&cullRect)) {
// 'asPoints' managed to find some fast path
SkPaint newP(paint);
newP.setPathEffect(nullptr);
newP.setStyle(SkPaint::kFill_Style);
if (!pointData.fFirst.isEmpty()) {
if (device) {
device->drawPath(pointData.fFirst, newP);
} else {
this->drawPath(pointData.fFirst, newP);
}
}
if (!pointData.fLast.isEmpty()) {
if (device) {
device->drawPath(pointData.fLast, newP);
} else {
this->drawPath(pointData.fLast, newP);
}
}
if (pointData.fSize.fX == pointData.fSize.fY) {
// The rest of the dashed line can just be drawn as points
SkASSERT(pointData.fSize.fX == SkScalarHalf(newP.getStrokeWidth()));
if (SkPathEffectBase::PointData::kCircles_PointFlag & pointData.fFlags) {
newP.setStrokeCap(SkPaint::kRound_Cap);
} else {
newP.setStrokeCap(SkPaint::kButt_Cap);
}
if (device) {
device->drawPoints(SkCanvas::kPoints_PointMode,
pointData.fNumPoints,
pointData.fPoints,
newP);
} else {
this->drawPoints(SkCanvas::kPoints_PointMode,
pointData.fNumPoints,
pointData.fPoints,
newP,
device);
}
break;
} else {
// The rest of the dashed line must be drawn as rects
SkASSERT(!(SkPathEffectBase::PointData::kCircles_PointFlag &
pointData.fFlags));
SkRect r;
for (int i = 0; i < pointData.fNumPoints; ++i) {
r.setLTRB(pointData.fPoints[i].fX - pointData.fSize.fX,
pointData.fPoints[i].fY - pointData.fSize.fY,
pointData.fPoints[i].fX + pointData.fSize.fX,
pointData.fPoints[i].fY + pointData.fSize.fY);
if (device) {
device->drawRect(r, newP);
} else {
this->drawRect(r, newP);
}
}
}
break;
}
}
[[fallthrough]]; // couldn't take fast path
case SkCanvas::kPolygon_PointMode: {
count -= 1;
SkPath path;
SkPaint p(paint);
p.setStyle(SkPaint::kStroke_Style);
size_t inc = (SkCanvas::kLines_PointMode == mode) ? 2 : 1;
path.setIsVolatile(true);
for (size_t i = 0; i < count; i += inc) {
path.moveTo(pts[i]);
path.lineTo(pts[i+1]);
if (device) {
device->drawPath(path, p, true);
} else {
this->drawPath(path, p, nullptr, true);
}
path.rewind();
}
break;
}
}
}
}
}
Device drawPoints
调用device(SkBaseDevice)的drawPoints方法,该方法由SkBaseDevice的子类Device实现:
//external/skia/experimental/graphite/src/Device.cpp
class Device final : public SkBaseDevice {
void Device::drawPoints(SkCanvas::PointMode mode, size_t count,
const SkPoint* points, const SkPaint& paint) {
// TODO: I'm [ml] not sure either CPU or GPU backend really has a fast path for this that
// isn't captured by drawOval and drawLine, so could easily be moved into SkCanvas.
if (mode == SkCanvas::kPoints_PointMode) {
float radius = 0.5f * paint.getStrokeWidth();
for (size_t i = 0; i < count; ++i) {
SkRect pointRect = SkRect::MakeLTRB(points[i].fX - radius, points[i].fY - radius,
points[i].fX + radius, points[i].fY + radius);
// drawOval/drawRect with a forced fill style
if (paint.getStrokeCap() == SkPaint::kRound_Cap) {
this->drawShape(Shape(SkRRect::MakeOval(pointRect)), paint, kFillStyle);
} else {
this->drawShape(Shape(pointRect), paint, kFillStyle);
}
}
} else {
// Force the style to be a stroke, using the radius and cap from the paint
SkStrokeRec stroke(paint, SkPaint::kStroke_Style);
size_t inc = (mode == SkCanvas::kLines_PointMode) ? 2 : 1;
for (size_t i = 0; i < count; i += inc) {
this->drawShape(Shape(points[i], points[(i + 1) % count]), paint, stroke);
}
}
}
}
调用Device的drawShape方法:
//external/skia/experimental/graphite/src/Device.cpp
void Device::drawShape(const Shape& shape,
const SkPaint& paint,
const SkStrokeRec& style,
Mask<DrawFlags> flags) {
// TODO: Device will cache the Transform or otherwise ensure it's computed once per change to
// its local-to-device matrix, but that requires updating SkDevice's virtuals. Right now we
// re-compute the Transform every draw, as well as any time we recurse on drawShape(), but that
// goes away with the caching.
Transform localToDevice(this->localToDevice44());
if (!localToDevice.valid()) {
// If the transform is not invertible or not finite then drawing isn't well defined.
SKGPU_LOG_W("Skipping draw with non-invertible/non-finite transform.");
return;
}
// Heavy weight paint options like path effects, mask filters, and stroke-and-fill style are
// applied on the CPU by generating a new shape and recursing on drawShape() with updated flags
if (!(flags & DrawFlags::kIgnorePathEffect) && paint.getPathEffect()) {
// Apply the path effect before anything else
// TODO: If asADash() returns true and the base path matches the dashing fast path, then
// that should be detected now as well. Maybe add dashPath to Device so canvas can handle it
SkStrokeRec newStyle = style;
newStyle.setResScale(localToDevice.maxScaleFactor());
SkPath dst;
if (paint.getPathEffect()->filterPath(&dst, shape.asPath(), &newStyle,
nullptr, localToDevice)) {
// Recurse using the path and new style, while disabling downstream path effect handling
this->drawShape(Shape(dst), paint, newStyle, flags | DrawFlags::kIgnorePathEffect);
return;
} else {
SKGPU_LOG_W("Path effect failed to apply, drawing original path.");
this->drawShape(shape, paint, style, flags | DrawFlags::kIgnorePathEffect);
return;
}
}
if (!(flags & DrawFlags::kIgnoreMaskFilter) && paint.getMaskFilter()) {
// TODO: Handle mask filters, ignored for the sprint.
// TODO: Could this be handled by SkCanvas by drawing a mask, blurring, and then sampling
// with a rect draw? What about fast paths for rrect blur masks...
this->drawShape(shape, paint, style, flags | DrawFlags::kIgnoreMaskFilter);
return;
}
// If we got here, then path effects and mask filters should have been handled and the style
// should be fill or stroke/hairline. Stroke-and-fill is not handled by DrawContext, but is
// emulated here by drawing twice--one stroke and one fill--using the same depth value.
SkASSERT(!SkToBool(paint.getPathEffect()) || (flags & DrawFlags::kIgnorePathEffect));
SkASSERT(!SkToBool(paint.getMaskFilter()) || (flags & DrawFlags::kIgnoreMaskFilter));
// Check if we have room to record into the current list before determining clipping and order
const SkStrokeRec::Style styleType = style.getStyle();
if (this->needsFlushBeforeDraw(styleType == SkStrokeRec::kStrokeAndFill_Style ? 2 : 1)) {
this->flushPendingWorkToRecorder();
}
DrawOrder order(fCurrentDepth.next());
auto [clip, clipOrder] = this->applyClipToDraw(localToDevice, shape, style, order.depth());
if (clip.drawBounds().isEmptyNegativeOrNaN()) {
// Clipped out, so don't record anything
return;
}
// A draw's order always depends on the clips that must be drawn before it
order.dependsOnPaintersOrder(clipOrder);
// If a draw is not opaque, it must be drawn after the most recent draw it intersects with in
// order to blend correctly. We always query the most recent draw (even when opaque) because it
// also lets Device easily track whether or not there are any overlapping draws.
PaintParams shading{paint};
const bool dependsOnDst = paint_depends_on_dst(shading);
CompressedPaintersOrder prevDraw =
fColorDepthBoundsManager->getMostRecentDraw(clip.drawBounds());
if (dependsOnDst) {
order.dependsOnPaintersOrder(prevDraw);
}
// TODO: if the chosen Renderer for a draw uses coverage AA, then it cannot be considered opaque
// regardless of what the PaintParams would do, but we won't know that until after the Renderer
// has been selected for the draw.
if (styleType == SkStrokeRec::kStroke_Style ||
styleType == SkStrokeRec::kHairline_Style ||
styleType == SkStrokeRec::kStrokeAndFill_Style) {
// TODO: If DC supports stroked primitives, Device could choose one of those based on shape
StrokeParams stroke(style.getWidth(), style.getMiter(), style.getJoin(), style.getCap());
fDC->strokePath(localToDevice, shape, stroke, clip, order, &shading);
}
if (styleType == SkStrokeRec::kFill_Style ||
styleType == SkStrokeRec::kStrokeAndFill_Style) {
// TODO: If DC supports filled primitives, Device could choose one of those based on shape
// TODO: Route all filled shapes to stencil-and-cover for the sprint; convex will draw
// correctly but uses an unnecessary stencil step.
// if (shape.convex()) {
// fDC->fillConvexPath(localToDevice, shape, clip, order, &shading);
// } else {
DisjointStencilIndex setIndex = fDisjointStencilSet->add(order.paintOrder(),
clip.drawBounds());
order.dependsOnStencil(setIndex);
fDC->stencilAndFillPath(localToDevice, shape, clip, order, &shading);
// }
}
// Record the painters order and depth used for this draw
const bool fullyOpaque = !dependsOnDst &&
shape.isRect() &&
localToDevice.type() <= Transform::Type::kRectStaysRect;
fColorDepthBoundsManager->recordDraw(shape.bounds(),
order.paintOrder(),
order.depth(),
fullyOpaque);
fCurrentDepth = order.depth();
fDrawsOverlap |= (prevDraw != DrawOrder::kNoIntersection);
}