2311skia,05绘制文本

2023-11-26 16:20
文章标签 05 绘制 文本 2311skia

本文主要是介绍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*32A8图片)解析文字,然后绘画模板,缓存模板.

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.主要是generateImagegeneratePath两个方法:
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,字体缓存管理

SkTypefaceSkia中的字体类,对应可有多种解析字体库实现.
因为安卓上面使用的是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/graphicframeworks/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绘制文本的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/426006

相关文章

C#使用DeepSeek API实现自然语言处理,文本分类和情感分析

《C#使用DeepSeekAPI实现自然语言处理,文本分类和情感分析》在C#中使用DeepSeekAPI可以实现多种功能,例如自然语言处理、文本分类、情感分析等,本文主要为大家介绍了具体实现步骤,... 目录准备工作文本生成文本分类问答系统代码生成翻译功能文本摘要文本校对图像描述生成总结在C#中使用Deep

通过C#获取PDF中指定文本或所有文本的字体信息

《通过C#获取PDF中指定文本或所有文本的字体信息》在设计和出版行业中,字体的选择和使用对最终作品的质量有着重要影响,然而,有时我们可能会遇到包含未知字体的PDF文件,这使得我们无法准确地复制或修改文... 目录引言C# 获取PDF中指定文本的字体信息C# 获取PDF文档中用到的所有字体信息引言在设计和出

使用Python绘制蛇年春节祝福艺术图

《使用Python绘制蛇年春节祝福艺术图》:本文主要介绍如何使用Python的Matplotlib库绘制一幅富有创意的“蛇年有福”艺术图,这幅图结合了数字,蛇形,花朵等装饰,需要的可以参考下... 目录1. 绘图的基本概念2. 准备工作3. 实现代码解析3.1 设置绘图画布3.2 绘制数字“2025”3.3

使用Python绘制可爱的招财猫

《使用Python绘制可爱的招财猫》招财猫,也被称为“幸运猫”,是一种象征财富和好运的吉祥物,经常出现在亚洲文化的商店、餐厅和家庭中,今天,我将带你用Python和matplotlib库从零开始绘制一... 目录1. 为什么选择用 python 绘制?2. 绘图的基本概念3. 实现代码解析3.1 设置绘图画

Python绘制土地利用和土地覆盖类型图示例详解

《Python绘制土地利用和土地覆盖类型图示例详解》本文介绍了如何使用Python绘制土地利用和土地覆盖类型图,并提供了详细的代码示例,通过安装所需的库,准备地理数据,使用geopandas和matp... 目录一、所需库的安装二、数据准备三、绘制土地利用和土地覆盖类型图四、代码解释五、其他可视化形式1.

如何用Python绘制简易动态圣诞树

《如何用Python绘制简易动态圣诞树》这篇文章主要给大家介绍了关于如何用Python绘制简易动态圣诞树,文中讲解了如何通过编写代码来实现特定的效果,包括代码的编写技巧和效果的展示,需要的朋友可以参考... 目录代码:效果:总结 代码:import randomimport timefrom math

Java操作xls替换文本或图片的功能实现

《Java操作xls替换文本或图片的功能实现》这篇文章主要给大家介绍了关于Java操作xls替换文本或图片功能实现的相关资料,文中通过示例代码讲解了文件上传、文件处理和Excel文件生成,需要的朋友可... 目录准备xls模板文件:template.xls准备需要替换的图片和数据功能实现包声明与导入类声明与

python解析HTML并提取span标签中的文本

《python解析HTML并提取span标签中的文本》在网页开发和数据抓取过程中,我们经常需要从HTML页面中提取信息,尤其是span元素中的文本,span标签是一个行内元素,通常用于包装一小段文本或... 目录一、安装相关依赖二、html 页面结构三、使用 BeautifulSoup javascript

【WebGPU Unleashed】1.1 绘制三角形

一部2024新的WebGPU教程,作者Shi Yan。内容很好,翻译过来与大家共享,内容上会有改动,加上自己的理解。更多精彩内容尽在 dt.sim3d.cn ,关注公众号【sky的数孪技术】,技术交流、源码下载请添加微信号:digital_twin123 在 3D 渲染领域,三角形是最基本的绘制元素。在这里,我们将学习如何绘制单个三角形。接下来我们将制作一个简单的着色器来定义三角形内的像素

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。