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,也要缓存.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/171442.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CSIT883系统分析与项目管理——Lecture4重点概念

文章目录 一、前言二、重点概念三、题目分析总结一、前言 这次课程给大家介绍信息收集、建模和工作流与项目范围管理相关的知识与题目。同时这节课还有一个重点内容——活动图,我打算单独写一篇文章进行讲解,感兴趣的同学可以留意一下哦! 二、重点概念 1.工作分解结构(W…

渗透测试【一】:渗透测试常见问题

渗透测试【一】&#xff1a;渗透测试常见问题 1、问题清单2、问题现象及解决办法2.1、点击劫持2.2、用户枚举2.3、Springboot未授权访问2.4、Swagger未授权访问2.5、Host头注入2.6、任意文件上传2.7、敏感路径泄露2.8、跨域资源共享2.9、Spring Cloud Gateway RCE2.10、Content…

某省交控高速公路分公司高效运维

客户背景与挑战 某省交通控股集团下面有一个高速公路分公司&#xff0c;负责运营和管理该省的高速公路网络。随着IT设备的增加和机房动环设备数量的增长&#xff0c;该公司在跨区域跨网络管理、平台升级和扩容方面遇到了挑战。为了解决这些问题&#xff0c;该公司选择使用监控易…

【挑战业余一周拿证】CSDN官方课程目录

一、亚马逊云科技简介 二、在云中计算 三、全球基础设施和可靠性 四、联网 五、存储和数据库 六、安全性 七、监控和分析 八、定价和支持 九、迁移和创新 十、云之旅 关注订阅号 CSDN 官方中文视频&#xff08;免费&#xff09;&#xff1a;点击进入 一、亚马逊云科…

女生儿童房装修:原木上下铺搭配粉色调。福州中宅装饰,福州装修

你是否正在为女生儿童房的装修而发愁呢&#xff1f;该如何让房间既适合孩子生活&#xff0c;又能够满足日常学习的需要呢&#xff1f;这里有一个精美的装修案例&#xff0c;或许能够为你提供一些灵感。 1️⃣ 原木上下铺 房间的上下铺采用了原木色调&#xff0c;带来了自然、温…

STM32 F1 串口空闲中断 + DMA实现数据发送

DMA实现数据发送 文章目录 DMA实现数据发送前言一、DMA二、代码编写1.DMA2.USART3.main 前言 当你遇到通信数据量大的时候&#xff0c;可以使用 空闲中断 DMA 的方案来减轻 CPU 的压力。 或者 在进行stm32开发时&#xff0c;有时会遇到这种情况&#xff1a;需要在设备间进行数…

1.1 C语言之入门:使用Visual Studio Community 2022运行hello world

1.1 使用Visual Studio Community 2022运行c语言的hello world 一、下载安装Visual Studio Community 2022 与 新建项目二、编写c helloworld三、编译、链接、运行 c helloworld1. 问题记录&#xff1a;无法打开源文件"stdio.h"2. 问题记录&#xff1a;调试和执行按钮…

Java入门基础:浅显易懂 do...while

文章目录 前言一、布尔表达式二、do...while三、语法四、示例 前言 do…while 在开发过程中其实并不常用&#xff0c;95%以上都是用 for 而不是 do…while。因为 do…while 能做的 for 能做&#xff0c;do…while 不能做的 for 也能做。所以对于 do…while 能够看懂代码即可 在…

【Linux】bash 终端指令

进程 $ ps aux | grep pwd work 63317 0.0 0.0 51192 612 pts/9 S 14:22 0:00 grep /home/work/search/1000000.dyenv-user-diaoyan-baiseCliPlus-baisePlus-195522.diaoyan.yq/ala-ac/output_root端口 查看本机端口开放情况 netstat -tln | grep :31 tcp …

Unsupervised Skill Discovery via Recurrent Skill Training论文笔记

Zheyuan Jiang, Jingyue Gao, Jianyu Chen (2022). Unsupervised Skill Discovery via Recurrent Skill Training. In Conference on Neural Information Processing Systems (NeurIPS), 2022. 通过循环技能训练发现无监督技能 1、Motivation 以往的无监督技能发现方法主要使…

Unity优化——脚本优化策略2

大家好&#xff0c;这里是七七&#xff0c;今天继续来介绍几个Unity脚本优化策略 一、更快的GameObject空引用检查 事实证明&#xff0c;对GameObject执行空引用检查会导致一些不必要的开销。与典型的C#对象相比&#xff0c;GameObject和MonoBehaviour是特殊对象&#xff0c;…

可燃气体监测仪助力燃气管网安全监测,效果一览

城市地下管线是指城市范围内供应水、排放水、燃气等各类管线及其附属设施&#xff0c;它们是保障城市正常运转的重要基础设施且影响着城市生命线。其中燃气引发的事故近些年不断增加&#xff0c;由于燃气管线深埋地下环境复杂&#xff0c;所以仅仅依赖人工巡查难以全面有效地防…

17. Python 数据库操作之MySQL和SQLite实例

目录 1. 简介2. 使用PyMySQL2. 使用SQLite 1. 简介 数据库种类繁多&#xff0c;每种数据库的对外接口实现各不相同&#xff0c;为了方便对数据库进行统一的操作&#xff0c;大部分编程语言都提供了标准化的数据库接口&#xff0c;用户不需要了解每种数据的接口实现细节&#x…

【每日一题】1457. 二叉树中的伪回文路径-2023.11.25

题目&#xff1a; 1457. 二叉树中的伪回文路径 给你一棵二叉树&#xff0c;每个节点的值为 1 到 9 。我们称二叉树中的一条路径是 「伪回文」的&#xff0c;当它满足&#xff1a;路径经过的所有节点值的排列中&#xff0c;存在一个回文序列。 请你返回从根到叶子节点的所有路…

代码随想录算法训练营第四十六天 | 139.单词拆分,多重背包,背包问题总结

目录 139.单词拆分 多重背包 背包问题总结 01背包 完全背包 多重背包 139.单词拆分 题目链接&#xff1a;139. 单词拆分 不要求字典中的单词全部使用&#xff0c;但是要求拆分的单词拆分成的每一个子串都是字典中的单词。 &#xff08;1&#xff09;dp[ i ] 表示前 i 个字符组成…

Re55:读论文 Entities as Experts: Sparse Memory Access with Entity Supervision

诸神缄默不语-个人CSDN博文目录 诸神缄默不语的论文阅读笔记和分类 论文名称&#xff1a;Entities as Experts: Sparse Memory Access with Entity Supervision 模型名称&#xff1a;Entities as Experts (EaE) ArXiv网址&#xff1a;https://arxiv.org/abs/2004.07202 本文…

人工智能基础_机器学习050_对比sigmoid函数和softmax函数的区别_两种分类器算法的区别---人工智能工作笔记0090

可以看到最上面是softmax的函数对吧,但是如果当k = 2 那么这个时候softmax的函数就可以退化为sigmoid函数,也就是 逻辑斯蒂回归了对吧 我们来看一下推导过程,可以看到上面是softmax的函数 可以看到k=2 表示,只有两个类别对吧,两个类别的分类不就是sigmoid函数嘛对吧,所以说 …

Python算法——霍夫曼编码树

Python中的霍夫曼编码树 霍夫曼编码是一种用于数据压缩的技术&#xff0c;通过构建霍夫曼编码树&#xff08;Huffman Tree&#xff09;来实现。这篇博客将详细讲解霍夫曼编码树的原理、构建方法和使用方式&#xff0c;并提供相应的Python代码实现。 霍夫曼编码原理 霍夫曼编…

ubuntu 安装 jetbrains-toolbox

ubuntu 安装 jetbrains-toolbox 官网下载 jetbrains-toolbox jetbrains 官网 jetbrains 官网&#xff1a;https://www.jetbrains.com/ jetbrains-toolbox 官网下载页面 在下载页面点击 Download 安装 jetbrains-toolbox 解压 jetbrains-toolbox 安装包 到指定目录 本案例将…

STM32 默认时钟更改 +debug调试

STM32时钟 文章目录 STM32时钟前言一、修改系统时钟二、DEBUG 前言 为什么我们要改STM32的时钟呢&#xff0c;打个比方在做SPI驱动的时候&#xff0c;需要16M的时钟&#xff0c;但是stm32默认是72的分频分不出来&#xff0c;这个时候我们就要改系统时钟了&#xff0c;那么怎么…