本文主要是介绍2311skia,05绘制文本,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
绘画文字
绘画文字主要包括转换编码(主要是中文
),解析
字形(点线或image
)和实际渲染
三个步骤.在该过程中,解析字形
和实际渲染
均是耗时步骤.
Skia
缓存解析文字
的结果.在中文
字较多,使用多种
字体,绘画风格(粗/斜体
)有变化时,该缓存
会很大,因此Skia
文字,限制了缓存的内存.
1,SkPaint
绘画文字
与SkPaint
的属性很相关
,先回头看下SkPaint
相关的属性
class SkPaint
{
privateSkTypeface* 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`时,连接各个路径片断时,期望圆滑连接阈值,`Join`类型为默认的`kMiter_Join`时无效//一组不超过`32`位的属性union {struct {//所有这些位域加起来应达到`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 = 0x100kLCDRenderText_Flag = 0x200kEmbeddedBitmapText_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;//记录改变了`哪些属性`,以便更新相关缓存
};
2
,字体绘画基本流程
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;}
}
(2)
按Mask
(32*32
的A8
图片)解析
文字,然后绘画
模板,缓存
模板.
SkDrawCacheProc glyphCacheProc = paint.getDrawCacheProc();
SkAutoGlyphCache autoCache(paint, &fDevice->fLeakyProperties, fMatrix);
SkGlyphCache* cache = autoCache.getCache();
//转变起点
{SkPoint loc;fMatrix->mapXY(x, y, &loc);x = loc.fX;y = loc.fY;
}
//要先测量
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
:负责解析
字形,有多种实现.安卓
中是用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());//因为`freetype-y-goes-up`和`skia-y-goes-down`,反向`dy`dy = -dy;}FT_Outline_Get_CBox(outline, &bbox);//真正想为`Subpixel`做的是//offset(dx, dy)//compute_bounds//offset(bbox & !63)//但这是两个调用`offset`,因此如下,只需一个调用`offset`即可实现相同目的.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);//假定没有其他格式.SkASSERT(FT_PIXEL_MODE_MONO == pixel_mode ||FT_PIXEL_MODE_GRAY == pixel_mode ||FT_PIXEL_MODE_BGRA == pixel_mode);//这些是此`ScalerContext`应请求的唯一格式.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 (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;}//否则,缩放位图.复制`FT_Bitmap`到`SkBitmap`(`A8`或`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);//除非字形的掩码是`BW`或`LCD`,否则在`位图`中,包装字形的掩码.`BW`需要一个`A8`目标来调整,然后可下采样它.`LCD`应使用`4xA8`目标,然后下采样它.为简单起见,`LCD`使用`A8`并复制它.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);}//缩放`unscaledBitmap`进`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);//如果目标是`BW`或`LCD`,则从`A8`转换.if (SkMask::kBW_Format == maskFormat) {//复制`A8`的`dstBitmap`进`A1`的`glyph.fImage`中.SkMask dstMask;glyph.toMask(&dstMask);packA8ToA1(dstMask, dstBitmap.getAddr8(0, 0), dstBitmap.rowBytes());} else if (SkMask::kLCD16_Format == maskFormat) {//复制`A8`的`dstBitmap`进`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;}//过去总是在预`USE_COLOR_LUMINANCE`,但有了`colorlum`,它是可选的
#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; //忽略嵌入的位图,这样就可确保得到轮廓flags &= ~FT_LOAD_RENDER; //不要扫描转换(只想要轮廓)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);//`FreeType`的路径原点总是是`水平布局`原点.如果需要,偏移路径,使其相对`垂直原点`.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
中的字体类
,对应可有多种解析字体库
实现.
因为安卓
上面使用的是FreeType
,因此也只讲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;
}
对安卓
,初化系统
时,在预加载
时就解析
所有字体文件,按SkFaceRec
包装,并保存为一个全局链表
.见(frameworks/base/graphic
和frameworks/base/core/jni
目录下面的代码)
public class Typeface {private static void init() {//加载字体配置并初化`Minikin`状态File systemFontConfigLocation = getSystemFontConfigLocation();File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);try {FileInputStream fontsIn = new FileInputStream(configFilename);FontListParser.Config fontConfig = FontListParser.parse(fontsIn);List<FontFamily> familyList = new ArrayList<FontFamily>();//注意,`默认字体`总是在`回退列表`中;这是对预`Minikin`行为的增强.for (int i = 0; i < fontConfig.families.size(); i++) {Family f = fontConfig.families.get(i);if (i == 0 || f.name == null) {familyList.add(makeFamilyFromParsed(f));}}sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);setDefault(Typeface.createFromFamilies(sFallbackFonts));Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();for (int i = 0; i < fontConfig.families.size(); i++) {Typeface typeface;Family f = fontConfig.families.get(i);if (f.name != null) {if (i == 0) {//第一项是默认字体;不必复制相应的`FontFamily`.typeface = sDefaultTypeface;} else {FontFamily fontFamily = makeFamilyFromParsed(f);FontFamily[] families = { fontFamily };typeface = Typeface.createFromFamiliesWithDefault(families);}systemFonts.put(f.name, typeface);}}for (FontListParser.Alias alias : fontConfig.aliases) {Typeface base = systemFonts.get(alias.toName);Typeface newFace = base;int weight = alias.weight;if (weight != 400) {newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));}systemFonts.put(alias.name, newFace);}sSystemFontMap = systemFonts;} catch (RuntimeException e) {Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);//`TODO`:在非`Minikin`时候正常,仅`Minikin`时,删除或出错} catch (FileNotFoundException e) {Log.e(TAG, "Error opening " + configFilename);} catch (IOException e) {Log.e(TAG, "Error reading " + configFilename);} catch (XmlPullParserException e) {Log.e(TAG, "XML parse exception for " + configFilename);}}static {init();//安装公开`API`中公开的`默认值和字体`DEFAULT = create((String) null, 0);DEFAULT_BOLD = create((String) null, Typeface.BOLD);SANS_SERIF = create("sans-serif", 0);SERIF = create("serif", 0);MONOSPACE = create("monospace", 0);sDefaults = new Typeface[] {DEFAULT,DEFAULT_BOLD,create((String) null, Typeface.ITALIC),create((String) null, Typeface.BOLD_ITALIC),};}
}
SkTypeface
记录一个字体的id
,使用时,到链表
中查出相关的字体.
一个字体和风格
,建一个SkGlyphCache
缓存,内含一个SkScalerContext
和一个SkGlyph
的哈希表,SkGlyph
缓存字体中解析字
出来的位图
.
此有内存容量
限制,超过
容量时,会清理
之前缓存的位图.Hash
冲突时,直接生成新字形
替换原字形
.
限制缓存
的内存宏
详见:src/core/SkGlyphCache_Globals.h.
和include/core/SkUserConfig.h
中的SK_DEFAULT_FONT_CACHE_LIMIT
宏
struct SkGlyph {void* fImage;SkPath* fPath;SkFixed fAdvanceX, fAdvanceY;uint32_t fID;uint16_t fWidth, fHeight;int16_t fTop, fLeft;void* fDistanceField;uint8_t fMaskFormat;int8_t fRsbDelta, fLsbDelta; //自动调整字距,时使用
};
绘画
字体只绘边界
,或缓存位图机制
不好处理时,按点线
解析字体,构成SkPath
,也要缓存.
这篇关于2311skia,05绘制文本的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!