原文出处:http://blog.csdn.net/hgl868/article/details/45584187
路径绘制尽管使用频率相对于图像绘制、文本绘制低,但却是非常重要的一个基本特性。所有不规则图形(椭圆、圆角矩形、三角形、简单的文字),最后都避不开路径绘制。
而且,若自己实现一个2D引擎,这块内容是很具有参考意义的,用OpenGL的话,图像采样等都很少关注了,对对坐标就好。但菱角、圆弧、曲线等如何绘制仍然是一个难题,这时就可以参考Skia中drawPath的实现。由于涉及较多的图形学知识,本章就不讲相关公式了,只讲讲基本的流程。
一、SkPath类
在之前的图像绘制并没有介绍SkBitmap,因为SkBitmap相对而言比较容易理解,网上文章也多。但这次的SkPath不同,研究它怎么用是需要一点精力的,因此在这里先做介绍。
1、SkPath结构
去除成员函数之后,我们看到SkPath包括这几个成员,注释中补充了说明:
class SK_API SkPath {
//SkPath中的主要内容,SkAutoTUnref是自解引用,之所以这么设计,是为了复制SkPath时,省去份量较多的点复制(只复制引用)。
//由一系列线段组成
SkAutoTUnref<SkPathRef> fPathRef;
int fLastMoveToIndex;
uint8_t fFillType;//如下四种类型之一
/*enum FillType {
kWinding_FillType,//绘制所有线段包围成的区域
kEvenOdd_FillType,//绘制被所有线段包围奇数次的区域)
kInverseWinding_FillType,//kWinding_FillType取反,即绘制不在该区域的点
kInverseEvenOdd_FillType//第二种type取反
}*/
mutable uint8_t fConvexity;//凹凸性,临时计算
mutable uint8_t fDirection;//方向,顺时针/逆时针,临时计算
#ifdef SK_BUILD_FOR_ANDROID
const SkPath* fSourcePath;//Hwui中使用,暂不关注
#endif
};
关于 fFillType中 kWinding_FillType和 kEvenOdd_FillType的区别,可看SkPath::contains。这是判断点是否在不规则几何体内的经典代码(),很有参考意义。
SkPathRef的内容如下:class SkPathRef
{
private:
mutable SkRect fBounds;//边界,临时计算
uint8_t fSegmentMask;//表示这个Path含有哪些种类的形状
mutable uint8_t fBoundsIsDirty;//缓存fBounds使用,表示 fBounds是否需要重新计算
mutable SkBool8 fIsFinite; // only meaningful if bounds are valid
mutable SkBool8 fIsOval;
/*skia不使用stl库而采用的一套容器方案,具体不细说,可看下 SkPath::Iter 的实现*/
SkPoint* fPoints; // points to begining of the allocation
uint8_t* fVerbs; // points just past the end of the allocation (verbs grow backwards)
int fVerbCnt;
int fPointCnt;
size_t fFreeSpace; // redundant but saves computation
SkTDArray<SkScalar> fConicWeights;
mutable uint32_t fGenerationID;
};
2、SkPath的主要类型:
kMove_Verb:表示需要移动起点
kLine_Verb:直线
kQuad_Verb:二次曲线
kConic_Verb:圆锥曲线
kCubic_Verb:三次曲线
kClose_Verb:表闭合到某点
kDone_Verb:表结束
#include "SkPath.h"
#include "SkCanvas.h"
#include "SkBitmap.h"
int main()
{
SkBitmap dst;
dst.allocN32Pixels(1000, 1000);
SkCanvas c(dst);
SkPath path;
/*一个三角形*/
path.moveTo(300,0);
path.lineTo(400,100);
path.lineTo(200,100);
path.close();
/*椭圆*/
SkRect oval;
oval.set(0, 0, 500, 600);
path.addOval(oval);
c.drawPath(path);
return 1;
}
二、drawPath流程
填充算法说明
我们跟进最重要的函数 sk_fill_path,如下为代码:
void sk_fill_path(const SkPath& path, const SkIRect* clipRect, SkBlitter* blitter,
int start_y, int stop_y, int shiftEdgesUp,
const SkRegion& clipRgn) {
SkASSERT(&path && blitter);
SkEdgeBuilder builder;
int count = builder.build(path, clipRect, shiftEdgesUp);
SkEdge** list = builder.edgeList();
if (count < 2) {
if (path.isInverseFillType()) {
/*
* Since we are in inverse-fill, our caller has already drawn above
* our top (start_y) and will draw below our bottom (stop_y). Thus
* we need to restrict our drawing to the intersection of the clip
* and those two limits.
*/
SkIRect rect = clipRgn.getBounds();
if (rect.fTop < start_y) {
rect.fTop = start_y;
}
if (rect.fBottom > stop_y) {
rect.fBottom = stop_y;
}
if (!rect.isEmpty()) {
blitter->blitRect(rect.fLeft << shiftEdgesUp,
rect.fTop << shiftEdgesUp,
rect.width() << shiftEdgesUp,
rect.height() << shiftEdgesUp);
}
}
return;
}
SkEdge headEdge, tailEdge, *last;
// this returns the first and last edge after they're sorted into a dlink list
SkEdge* edge = sort_edges(list, count, &last);
headEdge.fPrev = NULL;
headEdge.fNext = edge;
headEdge.fFirstY = kEDGE_HEAD_Y;
headEdge.fX = SK_MinS32;
edge->fPrev = &headEdge;
tailEdge.fPrev = last;
tailEdge.fNext = NULL;
tailEdge.fFirstY = kEDGE_TAIL_Y;
last->fNext = &tailEdge;
// now edge is the head of the sorted linklist
start_y <<= shiftEdgesUp;
stop_y <<= shiftEdgesUp;
if (clipRect && start_y < clipRect->fTop) {
start_y = clipRect->fTop;
}
if (clipRect && stop_y > clipRect->fBottom) {
stop_y = clipRect->fBottom;
}
InverseBlitter ib;
PrePostProc proc = NULL;
if (path.isInverseFillType()) {
ib.setBlitter(blitter, clipRgn.getBounds(), shiftEdgesUp);
blitter = &ib;
proc = PrePostInverseBlitterProc;
}
if (path.isConvex() && (NULL == proc)) {
walk_convex_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, NULL);
} else {
walk_edges(&headEdge, path.getFillType(), blitter, start_y, stop_y, proc);
}
}
不考虑 Inverse 的情况,主要就是两步:
(1)生成一系列边:SkEdge
(2)遍历渲染各边所围出来的区域
凸集的渲染比较简单,因为可以保证,任意两条边+闭合线所围成区域一定需要渲染:
(1)取初始的两条边,分别为:左和右。
(2)渲染左右边+闭合边所围成的区域(一般为三角,当两边平行时取矩形)
(3)迭代刷新左右两边(如果是曲线需要刷新多次)
static void walk_convex_edges(SkEdge* prevHead, SkPath::FillType,SkBlitter* blitter, int start_y, int stop_y,
PrePostProc proc) {
validate_sort(prevHead->fNext);
SkEdge* leftE = prevHead->fNext;
SkEdge* riteE = leftE->fNext;
SkEdge* currE = riteE->fNext;
#if 0
int local_top = leftE->fFirstY;
SkASSERT(local_top == riteE->fFirstY);
#else
// our edge choppers for curves can result in the initial edges
// not lining up, so we take the max.
int local_top = SkMax32(leftE->fFirstY, riteE->fFirstY);
#endif
SkASSERT(local_top >= start_y);
for (;;) {
SkASSERT(leftE->fFirstY <= stop_y);
SkASSERT(riteE->fFirstY <= stop_y);
if (leftE->fX > riteE->fX || (leftE->fX == riteE->fX &&
leftE->fDX > riteE->fDX)) {
SkTSwap(leftE, riteE);
}
int local_bot = SkMin32(leftE->fLastY, riteE->fLastY);
local_bot = SkMin32(local_bot, stop_y - 1);
SkASSERT(local_top <= local_bot);
SkFixed left = leftE->fX;
SkFixed dLeft = leftE->fDX;
SkFixed rite = riteE->fX;
SkFixed dRite = riteE->fDX;
int count = local_bot - local_top;
SkASSERT(count >= 0);
if (0 == (dLeft | dRite)) {
int L = SkFixedRoundToInt(left);
int R = SkFixedRoundToInt(rite);
if (L < R) {
count += 1;
blitter->blitRect(L, local_top, R - L, count);
left += count * dLeft;
rite += count * dRite;
}
local_top = local_bot + 1;
} else {
do {
int L = SkFixedRoundToInt(left);
int R = SkFixedRoundToInt(rite);
if (L < R) {
blitter->blitH(L, local_top, R - L);
}
left += dLeft;
rite += dRite;
local_top += 1;
} while (--count >= 0);
}
leftE->fX = left;
riteE->fX = rite;
if (update_edge(leftE, local_bot)) {
if (currE->fFirstY >= stop_y) {
break;
}
leftE = currE;
currE = currE->fNext;
}
if (update_edge(riteE, local_bot)) {
if (currE->fFirstY >= stop_y) {
break;
}
riteE = currE;
currE = currE->fNext;
}
SkASSERT(leftE);
SkASSERT(riteE);
// check our bottom clip
SkASSERT(local_top == local_bot + 1);
if (local_top >= stop_y) {
break;
}
}
}
凹集或者判断不了凹凸性就比较复杂,需要一条线一条线去渲染,每次渲染还得判断奇偶性:
代码如下,不分析了:
static void walk_edges(SkEdge* prevHead, SkPath::FillType fillType,SkBlitter* blitter, int start_y, int stop_y,
PrePostProc proc) {
validate_sort(prevHead->fNext);
int curr_y = start_y;
// returns 1 for evenodd, -1 for winding, regardless of inverse-ness
int windingMask = (fillType & 1) ? 1 : -1;
for (;;) {
int w = 0;
int left SK_INIT_TO_AVOID_WARNING;
bool in_interval = false;
SkEdge* currE = prevHead->fNext;
SkFixed prevX = prevHead->fX;
validate_edges_for_y(currE, curr_y);
if (proc) {
proc(blitter, curr_y, PREPOST_START); // pre-proc
}
while (currE->fFirstY <= curr_y) {
SkASSERT(currE->fLastY >= curr_y);
int x = SkFixedRoundToInt(currE->fX);
w += currE->fWinding;
if ((w & windingMask) == 0) { // we finished an interval
SkASSERT(in_interval);
int width = x - left;
SkASSERT(width >= 0);
if (width)
blitter->blitH(left, curr_y, width);
in_interval = false;
} else if (!in_interval) {
left = x;
in_interval = true;
}
SkEdge* next = currE->fNext;
SkFixed newX;
if (currE->fLastY == curr_y) { // are we done with this edge?
if (currE->fCurveCount < 0) {
if (((SkCubicEdge*)currE)->updateCubic()) {
SkASSERT(currE->fFirstY == curr_y + 1);
newX = currE->fX;
goto NEXT_X;
}
} else if (currE->fCurveCount > 0) {
if (((SkQuadraticEdge*)currE)->updateQuadratic()) {
newX = currE->fX;
goto NEXT_X;
}
}
remove_edge(currE);
} else {
SkASSERT(currE->fLastY > curr_y);
newX = currE->fX + currE->fDX;
currE->fX = newX;
NEXT_X:
if (newX < prevX) { // ripple currE backwards until it is x-sorted
backward_insert_edge_based_on_x(currE SkPARAM(curr_y));
} else {
prevX = newX;
}
}
currE = next;
SkASSERT(currE);
}
if (proc) {
proc(blitter, curr_y, PREPOST_END); // post-proc
}
curr_y += 1;
if (curr_y >= stop_y) {
break;
}
// now currE points to the first edge with a Yint larger than curr_y
insert_new_edges(currE, curr_y);
}
}
drawPath是绘制所有不规则形体的函数,带入Bitmap的Shader,可以制作不规则形体的图片。对于凸集,Skia的渲染主要也是切成三角片后渲染,和OpenGL类似。而对于凹集,则是扫描线了。渲染的实现和绘制图片一样,构建Blitter,调用Blitter的blit函数族渲染。
文字绘制
文字绘制主要包括编码转换(主要是中文)、字形解析(点线或image)和实际渲染三个步骤。在这个过程中,字形解析和实际渲染均是耗时步骤。Skia对文字解析的结果做了一套缓存机制。在中文字较多,使用多种字体,绘制的样式(粗/斜体)有变化时,这个缓存会变得很大,因此Skia文字缓存做了内存上的限制。
1、SkPaint
文字绘制与SkPaint的属性相关很大,先回头看下SkPaint相关的属性
class SkPaint{
private
SkTypeface* fTypeface;//字体
SkPathEffect* fPathEffect;//路径绘制效果
SkShader* fShader;//取色器
SkXfermode* fXfermode;//混合模式,类似OpenGL里面的Blend设置
SkColorFilter* fColorFilter;//图像绘制时,自定义图像采样函数时使用
SkMaskFilter* fMaskFilter;//路径绘制时,按有无像素做进一步自定义改进处理时使用
SkRasterizer* fRasterizer;//路径绘制时自定义生成像素点的算法时使用
SkDrawLooper* fLooper;//循环绘制,SkCanvas里面的第二重循环,一般不用关注
SkImageFilter* fImageFilter;//SkCanvas的第一重循环,绘制后做后处理用,一般不用关注
SkAnnotation* fAnnotation;//暂时没用到的属性
SkScalar fTextSize;//文字大小
SkScalar fTextScaleX;//文字水平方向上的拉伸,仅用于PDF绘制
SkScalar fTextSkewX;//文字横向扭曲度,仅用于PDF绘制
SkColor fColor;//纯色,在fShader为空时使用
SkScalar fWidth;//带边界时(kStroke_Style/kStrokeAndFill_Style)生效,边界的宽度
SkScalar fMiterLimit;//drawPath时,连接各个path片断时,要求的圆滑连接阈值,Join 类型为默认的kMiter_Join时无效
/*一组不超过32位的属性*/
union {
struct {
// all of these bitfields should add up to 32
unsigned fFlags : 16;//包含所有的0/1二值属性:
/*
kAntiAlias_Flag = 0x01,//是否抗锯齿
kDither_Flag = 0x04,//是否做抖动处理
kUnderlineText_Flag = 0x08,//是否绘制文字下划线
kStrikeThruText_Flag = 0x10,//目前未看到其作用
kFakeBoldText_Flag = 0x20,
kLinearText_Flag = 0x40,
kSubpixelText_Flag = 0x80,//文字像素精确采样
kDevKernText_Flag = 0x100
kLCDRenderText_Flag = 0x200
kEmbeddedBitmapText_Flag = 0x400,
kAutoHinting_Flag = 0x800,
kVerticalText_Flag = 0x1000,//是否竖向绘制文字
kGenA8FromLCD_Flag = 0x2000,
kDistanceFieldTextTEMP_Flag = 0x4000,
kAllFlags = 0xFFFF
*/
unsigned fTextAlign : 2;//文字对齐方式,取值如下:
/*
enum Align {
kLeft_Align,//左对齐
kCenter_Align,//居中
kRight_Align,//右对齐
};
*/
unsigned fCapType : 2;//边界连接类型,分无连接,圆角连接,半方形连接
unsigned fJoinType : 2;//Path片断连接类型
unsigned fStyle : 2;//绘制模式,填充边界/区域
/*
enum Style {
kFill_Style, //填充区域
kStroke_Style,//绘制边界
kStrokeAndFill_Style,//填充区域并绘制边界
};
*/
unsigned fTextEncoding : 2;//文字编码格式,支持如下几种
enum TextEncoding {
kUTF8_TextEncoding,//utf-8,默认格式
kUTF16_TextEncoding,
kUTF32_TextEncoding,
kGlyphID_TextEncoding
};
unsigned fHinting : 2;
unsigned fFilterLevel : 2;//在图像绘制时提到的采样质量要求
//unsigned fFreeBits : 2;
};
uint32_t fBitfields;
};
uint32_t fDirtyBits;//记录哪些属性被改变了,以便更新相关的缓存
};
SkCanvas
绘制文字和下划线
SkDraw
两种绘制方式:
(1)将文字解析为路径,然后绘制路径,缓存路径(drawText_asPaths)。
void SkDraw::drawText_asPaths(const char text[], size_t byteLength,SkScalar x, SkScalar y,
const SkPaint& paint) const {
SkDEBUGCODE(this->validate();)
SkTextToPathIter iter(text, byteLength, paint, true);
SkMatrix matrix;
matrix.setScale(iter.getPathScale(), iter.getPathScale());
matrix.postTranslate(x, y);
const SkPath* iterPath;
SkScalar xpos, prevXPos = 0;
while (iter.next(&iterPath, &xpos)) {
matrix.postTranslate(xpos - prevXPos, 0);
if (iterPath) {
const SkPaint& pnt = iter.getPaint();
if (fDevice) {
fDevice->drawPath(*this, *iterPath, pnt, &matrix, false);
} else {
this->drawPath(*iterPath, pnt, &matrix, false);
}
}
prevXPos = xpos;
}
}
将文字解析为Mask(32*32的A8图片),然后绘制模板,缓存模板。
SkDrawCacheProc glyphCacheProc = paint.getDrawCacheProc();
SkAutoGlyphCache autoCache(paint, &fDevice->fLeakyProperties, fMatrix);
SkGlyphCache* cache = autoCache.getCache();
// transform our starting point
{
SkPoint loc;
fMatrix->mapXY(x, y, &loc);
x = loc.fX;
y = loc.fY;
}
// need to measure first
if (paint.getTextAlign() != SkPaint::kLeft_Align) {
SkVector stop;
measure_text(cache, glyphCacheProc, text, byteLength, &stop);
SkScalar stopX = stop.fX;
SkScalar stopY = stop.fY;
if (paint.getTextAlign() == SkPaint::kCenter_Align) {
stopX = SkScalarHalf(stopX);
stopY = SkScalarHalf(stopY);
}
x -= stopX;
y -= stopY;
}
const char* stop = text + byteLength;
SkAAClipBlitter aaBlitter;
SkAutoBlitterChoose blitterChooser;
SkBlitter* blitter = NULL;
if (needsRasterTextBlit(*this)) {
blitterChooser.choose(*fBitmap, *fMatrix, paint);
blitter = blitterChooser.get();
if (fRC->isAA()) {
aaBlitter.init(blitter, &fRC->aaRgn());
blitter = &aaBlitter;
}
}
SkAutoKern autokern;
SkDraw1Glyph d1g;
SkDraw1Glyph::Proc proc = d1g.init(this, blitter, cache, paint);
SkFixed fxMask = ~0;
SkFixed fyMask = ~0;
if (cache->isSubpixel()) {
SkAxisAlignment baseline = SkComputeAxisAlignmentForHText(*fMatrix);
if (kX_SkAxisAlignment == baseline) {
fyMask = 0;
d1g.fHalfSampleY = SK_FixedHalf;
} else if (kY_SkAxisAlignment == baseline) {
fxMask = 0;
d1g.fHalfSampleX = SK_FixedHalf;
}
}
SkFixed fx = SkScalarToFixed(x) + d1g.fHalfSampleX;
SkFixed fy = SkScalarToFixed(y) + d1g.fHalfSampleY;
while (text < stop) {
const SkGlyph& glyph = glyphCacheProc(cache, &text, fx & fxMask, fy & fyMask);
fx += autokern.adjust(glyph);
if (glyph.fWidth) {
proc(d1g, fx, fy, glyph);
}
fx += glyph.fAdvanceX;
fy += glyph.fAdvanceY;
}
cacheProc是翻译字符编码的函数,由SkPaint::getDrawCacheProc产生:
SkDrawCacheProc SkPaint::getDrawCacheProc() const {
static const SkDrawCacheProc gDrawCacheProcs[] = {
sk_getMetrics_utf8_00,
sk_getMetrics_utf16_00,
sk_getMetrics_utf32_00,
sk_getMetrics_glyph_00,
sk_getMetrics_utf8_xy,
sk_getMetrics_utf16_xy,
sk_getMetrics_utf32_xy,
sk_getMetrics_glyph_xy
};
unsigned index = this->getTextEncoding();
if (fFlags & kSubpixelText_Flag) {
index += 4;
}
SkASSERT(index < SK_ARRAY_COUNT(gDrawCacheProcs));
return gDrawCacheProcs[index];
}
SkGlyphCache:
字形解析的结果缓存。
SkScalerContext:
负责字形的解析,有多种实现。Android中是用FreeType:SkScalerContext_FreeType。主要是generateImage和generatePath两个方法:
generateImage:
void SkScalerContext_FreeType::generateImage(const SkGlyph& glyph) {SkAutoMutexAcquire ac(gFTMutex);
FT_Error err;
if (this->setupSize()) {
goto ERROR;
}
err = FT_Load_Glyph( fFace, glyph.getGlyphID(fBaseGlyphCount), fLoadGlyphFlags);
if (err != 0) {
SkDEBUGF(("SkScalerContext_FreeType::generateImage: FT_Load_Glyph(glyph:%d width:%d height:%d rb:%d flags:%d) returned 0x%x\n",
glyph.getGlyphID(fBaseGlyphCount), glyph.fWidth, glyph.fHeight, glyph.rowBytes(), fLoadGlyphFlags, err));
ERROR:
memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
return;
}
emboldenIfNeeded(fFace, fFace->glyph);
generateGlyphImage(fFace, glyph);
}
void SkScalerContext_FreeType_Base::generateGlyphImage(FT_Face face, const SkGlyph& glyph) {
const bool doBGR = SkToBool(fRec.fFlags & SkScalerContext::kLCD_BGROrder_Flag);
const bool doVert = SkToBool(fRec.fFlags & SkScalerContext::kLCD_Vertical_Flag);
switch ( face->glyph->format ) {
case FT_GLYPH_FORMAT_OUTLINE: {
FT_Outline* outline = &face->glyph->outline;
FT_BBox bbox;
FT_Bitmap target;
int dx = 0, dy = 0;
if (fRec.fFlags & SkScalerContext::kSubpixelPositioning_Flag) {
dx = SkFixedToFDot6(glyph.getSubXFixed());
dy = SkFixedToFDot6(glyph.getSubYFixed());
// negate dy since freetype-y-goes-up and skia-y-goes-down
dy = -dy;
}
FT_Outline_Get_CBox(outline, &bbox);
/*
what we really want to do for subpixel is
offset(dx, dy)
compute_bounds
offset(bbox & !63)
but that is two calls to offset, so we do the following, which
achieves the same thing with only one offset call.
*/
FT_Outline_Translate(outline, dx - ((bbox.xMin + dx) & ~63),
dy - ((bbox.yMin + dy) & ~63));
if (SkMask::kLCD16_Format == glyph.fMaskFormat) {
FT_Render_Glyph(face->glyph, doVert ? FT_RENDER_MODE_LCD_V : FT_RENDER_MODE_LCD);
SkMask mask;
glyph.toMask(&mask);
if (fPreBlend.isApplicable()) {
copyFT2LCD16<true>(face->glyph->bitmap, mask, doBGR,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
} else {
copyFT2LCD16<false>(face->glyph->bitmap, mask, doBGR,
fPreBlend.fR, fPreBlend.fG, fPreBlend.fB);
}
} else {
target.width = glyph.fWidth;
target.rows = glyph.fHeight;
target.pitch = glyph.rowBytes();
target.buffer = reinterpret_cast<uint8_t*>(glyph.fImage);
target.pixel_mode = compute_pixel_mode( (SkMask::Format)fRec.fMaskFormat);
target.num_grays = 256;
memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
FT_Outline_Get_Bitmap(face->glyph->library, outline, &target);
}
} break;
case FT_GLYPH_FORMAT_BITMAP: {
FT_Pixel_Mode pixel_mode = static_cast<FT_Pixel_Mode>(face->glyph->bitmap.pixel_mode);
SkMask::Format maskFormat = static_cast<SkMask::Format>(glyph.fMaskFormat);
// Assume that the other formats do not exist.
SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode ||
FT_PIXEL_MODE_GRAY == pixel_mode ||
FT_PIXEL_MODE_BGRA == pixel_mode);
// These are the only formats this ScalerContext should request.
SkASSERT(SkMask::kBW_Format == maskFormat ||
SkMask::kA8_Format == maskFormat ||
SkMask::kARGB32_Format == maskFormat ||
SkMask::kLCD16_Format == maskFormat);
if (fRec.fFlags & SkScalerContext::kEmbolden_Flag &&
!(face->style_flags & FT_STYLE_FLAG_BOLD))
{
FT_GlyphSlot_Own_Bitmap(face->glyph);
FT_Bitmap_Embolden(face->glyph->library, &face->glyph->bitmap,
kBitmapEmboldenStrength, 0);
}
// If no scaling needed, directly copy glyph bitmap.
if (glyph.fWidth == face->glyph->bitmap.width &&
glyph.fHeight == face->glyph->bitmap.rows &&
glyph.fTop == -face->glyph->bitmap_top &&
glyph.fLeft == face->glyph->bitmap_left)
{
SkMask dstMask;
glyph.toMask(&dstMask);
copyFTBitmap(face->glyph->bitmap, dstMask);
break;
}
// Otherwise, scale the bitmap.
// Copy the FT_Bitmap into an SkBitmap (either A8 or ARGB)
SkBitmap unscaledBitmap;
unscaledBitmap.allocPixels(SkImageInfo::Make(face->glyph->bitmap.width,
face->glyph->bitmap.rows,
SkColorType_for_FTPixelMode(pixel_mode),
kPremul_SkAlphaType));
SkMask unscaledBitmapAlias;
unscaledBitmapAlias.fImage = reinterpret_cast<uint8_t*>(unscaledBitmap.getPixels());
unscaledBitmapAlias.fBounds.set(0, 0, unscaledBitmap.width(), unscaledBitmap.height());
unscaledBitmapAlias.fRowBytes = unscaledBitmap.rowBytes();
unscaledBitmapAlias.fFormat = SkMaskFormat_for_SkColorType(unscaledBitmap.colorType());
copyFTBitmap(face->glyph->bitmap, unscaledBitmapAlias);
// Wrap the glyph's mask in a bitmap, unless the glyph's mask is BW or LCD.
// BW requires an A8 target for resizing, which can then be down sampled.
// LCD should use a 4x A8 target, which will then be down sampled.
// For simplicity, LCD uses A8 and is replicated.
int bitmapRowBytes = 0;
if (SkMask::kBW_Format != maskFormat && SkMask::kLCD16_Format != maskFormat) {
bitmapRowBytes = glyph.rowBytes();
}
SkBitmap dstBitmap;
dstBitmap.setInfo(SkImageInfo::Make(glyph.fWidth, glyph.fHeight,
SkColorType_for_SkMaskFormat(maskFormat),
kPremul_SkAlphaType),
bitmapRowBytes);
if (SkMask::kBW_Format == maskFormat || SkMask::kLCD16_Format == maskFormat) {
dstBitmap.allocPixels();
} else {
dstBitmap.setPixels(glyph.fImage);
}
// Scale unscaledBitmap into dstBitmap.
SkCanvas canvas(dstBitmap);
canvas.clear(SK_ColorTRANSPARENT);
canvas.scale(SkIntToScalar(glyph.fWidth) / SkIntToScalar(face->glyph->bitmap.width),
SkIntToScalar(glyph.fHeight) / SkIntToScalar(face->glyph->bitmap.rows));
SkPaint paint;
paint.setFilterLevel(SkPaint::kMedium_FilterLevel);
canvas.drawBitmap(unscaledBitmap, 0, 0, &paint);
// If the destination is BW or LCD, convert from A8.
if (SkMask::kBW_Format == maskFormat) {
// Copy the A8 dstBitmap into the A1 glyph.fImage.
SkMask dstMask;
glyph.toMask(&dstMask);
packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes());
} else if (SkMask::kLCD16_Format == maskFormat) {
// Copy the A8 dstBitmap into the LCD16 glyph.fImage.
uint8_t* src = dstBitmap.getAddr8(0, 0);
uint16_t* dst = reinterpret_cast<uint16_t*>(glyph.fImage);
for (int y = dstBitmap.height(); y --> 0;) {
for (int x = 0; x < dstBitmap.width(); ++x) {
dst[x] = grayToRGB16(src[x]);
}
dst = (uint16_t*)((char*)dst + glyph.rowBytes());
src += dstBitmap.rowBytes();
}
}
} break;
default:
SkDEBUGFAIL("unknown glyph format");
memset(glyph.fImage, 0, glyph.rowBytes() * glyph.fHeight);
return;
}
// We used to always do this pre-USE_COLOR_LUMINANCE, but with colorlum,
// it is optional
#if defined(SK_GAMMA_APPLY_TO_A8)
if (SkMask::kA8_Format == glyph.fMaskFormat && fPreBlend.isApplicable()) {
uint8_t* SK_RESTRICT dst = (uint8_t*)glyph.fImage;
unsigned rowBytes = glyph.rowBytes();
for (int y = glyph.fHeight - 1; y >= 0; --y) {
for (int x = glyph.fWidth - 1; x >= 0; --x) {
dst[x] = fPreBlend.fG[dst[x]];
}
dst += rowBytes;
}
}
#endif
}
generatePath:
void SkScalerContext_FreeType::generatePath(const SkGlyph& glyph,
SkPath* path) {
SkAutoMutexAcquire ac(gFTMutex);
SkASSERT(&glyph && path);
if (this->setupSize()) {
path->reset();
return;
}
uint32_t flags = fLoadGlyphFlags;
flags |= FT_LOAD_NO_BITMAP; // ignore embedded bitmaps so we're sure to get the outline
flags &= ~FT_LOAD_RENDER; // don't scan convert (we just want the outline)
FT_Error err = FT_Load_Glyph( fFace, glyph.getGlyphID(fBaseGlyphCount), flags);
if (err != 0) {
SkDEBUGF(("SkScalerContext_FreeType::generatePath: FT_Load_Glyph(glyph:%d flags:%d) returned 0x%x\n",
glyph.getGlyphID(fBaseGlyphCount), flags, err));
path->reset();
return;
}
emboldenIfNeeded(fFace, fFace->glyph);
generateGlyphPath(fFace, path);
// The path's origin from FreeType is always the horizontal layout origin.
// Offset the path so that it is relative to the vertical origin if needed.
if (fRec.fFlags & SkScalerContext::kVertical_Flag) {
FT_Vector vector;
vector.x = fFace->glyph->metrics.vertBearingX - fFace->glyph->metrics.horiBearingX;
vector.y = -fFace->glyph->metrics.vertBearingY - fFace->glyph->metrics.horiBearingY;
FT_Vector_Transform(&vector, &fMatrix22);
path->offset(SkFDot6ToScalar(vector.x), -SkFDot6ToScalar(vector.y));
}
}
3、字体缓存管理
SkTypeface是Skia中的字体类,对应可有多种字体库解析实现。
由于Android上面使用的是FreeType,因此也只讲FreeType分支。
SkTypeface* SkTypeface::CreateFromStream(SkStream* stream) {
return SkFontHost::CreateTypefaceFromStream(stream);
}
bool find_name_and_attributes(SkStream* stream, SkString* name,
SkTypeface::Style* style, bool* isFixedPitch) {
FT_Library library;
if (FT_Init_FreeType(&library)) {
return false;
}
FT_Open_Args args;
memset(&args, 0, sizeof(args));
const void* memoryBase = stream->getMemoryBase();
FT_StreamRec streamRec;
if (NULL != memoryBase) {
args.flags = FT_OPEN_MEMORY;
args.memory_base = (const FT_Byte*)memoryBase;
args.memory_size = stream->getLength();
} else {
memset(&streamRec, 0, sizeof(streamRec));
streamRec.size = stream->getLength();
streamRec.descriptor.pointer = stream;
streamRec.read = sk_stream_read;
streamRec.close = sk_stream_close;
args.flags = FT_OPEN_STREAM;
args.stream = &streamRec;
}
FT_Face face;
if (FT_Open_Face(library, &args, 0, &face)) {
FT_Done_FreeType(library);
return false;
}
int tempStyle = SkTypeface::kNormal;
if (face->style_flags & FT_STYLE_FLAG_BOLD) {
tempStyle |= SkTypeface::kBold;
}
if (face->style_flags & FT_STYLE_FLAG_ITALIC) {
tempStyle |= SkTypeface::kItalic;
}
if (name) {
name->set(face->family_name);
}
if (style) {
*style = (SkTypeface::Style) tempStyle;
}
if (isFixedPitch) {
*isFixedPitch = FT_IS_FIXED_WIDTH(face);
}
FT_Done_Face(face);
FT_Done_FreeType(library);
return true;
}
当绘制字体只绘边界或者位图缓存机制不好处理时,将字体解析成点线,构成SkPath,也做缓存。