Android使用OpenGL和FreeType绘制文字

Open GL主要是渲染图形的,有时候需要绘制文字,网上搜了一下,基本思路都是把文字转成位图,再使用Open GL纹理进行渲染。加载纹理在特定阶段才能成功(在onSurfaceCreated中加载),这样就无法动态的绘制字符串,一种方式是把可能用到的字符都加载到一个位图,渲染纹理的时候不同的字符就渲染纹理的特定区域,另一种方式就是每个字符生成一个位图(本文提供的代码就是这种方式)。

1、集成FreeType

这里我们直接使用源码集成 下载FreeType源码

新建一个 Android Native Library 类型的 Module 或者点击 File -> Add C++ to Module,下载的FreeType源码解压后文件夹改成 freetype,然后把整个文件夹复制到 cpp 目录,在 cpp 目录下的 CMakeLists.txt 中添加 freetype:

add_subdirectory(freetype)
target_link_libraries(${CMAKE_PROJECT_NAME}# List libraries link to the target libraryandroidlogfreetype)

我建的Module名称是 jfreetype,实现的代码主要有:

jfreetype.cpp

#include <jni.h>
#include <string>
#include <android//log.h>
#include "ft2build.h"
#include FT_FREETYPE_H#define LOG_I(...) __android_log_print(ANDROID_LOG_INFO, "NDK FT", __VA_ARGS__)
#define LOG_W(...) __android_log_print(ANDROID_LOG_WARN, "NDK FT", __VA_ARGS__)
#define LOG_E(...) __android_log_print(ANDROID_LOG_ERROR, "NDK FT", __VA_ARGS__)// https://freetype.org/freetype2/docs/tutorialFT_Library library;   /* handle to library     */
FT_Face face;      /* handle to face object */extern "C" JNIEXPORT jint JNICALL
Java_site_feiyuliuxing_jfreetype_JFreeType_init(JNIEnv *env,jobject, jobject face_buffer) {std::string hello = "Hello from C++";FT_Error error = FT_Init_FreeType(&library);if (error) {LOG_E("an error occurred during library initialization, error: %d", error);return error;}jbyte *buffer = (jbyte *) (env->GetDirectBufferAddress(face_buffer));jlong size = env->GetDirectBufferCapacity(face_buffer);error = FT_New_Memory_Face(library,(FT_Byte *) buffer,    /* first byte in memory */size,      /* size in bytes        */0,         /* face_index           */&face);if (error) {LOG_E("an error occurred during FT_New_Memory_Face, error: %d", error);return error;}error = FT_Set_Pixel_Sizes(face,   /* handle to face object */0,      /* pixel_width           */128);   /* pixel_height          */if (error) {LOG_E("an error occurred during FT_Set_Pixel_Sizes, error: %d", error);return error;}return 0;
}extern "C"
JNIEXPORT jint JNICALL
Java_site_feiyuliuxing_jfreetype_JFreeType_charBitmap(JNIEnv *env, jobject thiz,jobject ft_bitmap, jchar charcode) {FT_UInt glyph_index = FT_Get_Char_Index(face, charcode);FT_Error error = FT_Load_Glyph(face,          /* handle to face object */glyph_index,   /* glyph index           */FT_LOAD_DEFAULT);  /* load flags, see below */if (error) {LOG_E("an error occurred during FT_Get_Char_Index, error: %d", error);return error;}error = FT_Render_Glyph(face->glyph,   /* glyph slot  */FT_RENDER_MODE_NORMAL); /* render mode */if (error) {LOG_E("an error occurred during FT_Render_Glyph, error: %d", error);return error;}FT_Bitmap bitmap = face->glyph->bitmap;LOG_I("--------------- %c ---------------", charcode);LOG_I("FT_Bitmap size: %d x %d", bitmap.width, bitmap.rows);LOG_I("FT_Bitmap pixel mode: %d", bitmap.pixel_mode);LOG_I("FT_Bitmap bitmap top: %d", face->glyph->bitmap_top);LOG_I("metrics.height: %ld", face->glyph->metrics.height);LOG_I("metrics.horiBearingY: %ld", face->glyph->metrics.horiBearingY);jclass bmpCls = env->GetObjectClass(ft_bitmap);jfieldID rowsID = env->GetFieldID(bmpCls, "rows", "I");jfieldID widthID = env->GetFieldID(bmpCls, "width", "I");jfieldID bufferID = env->GetFieldID(bmpCls, "buffer", "[B");jfieldID leftID = env->GetFieldID(bmpCls, "bitmapLeft", "I");jfieldID topID = env->GetFieldID(bmpCls, "bitmapTop", "I");env->SetIntField(ft_bitmap, rowsID, (int) bitmap.rows);env->SetIntField(ft_bitmap, widthID, (int) bitmap.width);env->SetIntField(ft_bitmap, leftID, face->glyph->bitmap_left);env->SetIntField(ft_bitmap, topID, face->glyph->bitmap_top);int dataLength = bitmap.rows * bitmap.width;jbyteArray buf = env->NewByteArray(dataLength);jbyte *data = env->GetByteArrayElements(buf, nullptr);for (int i = 0; i < dataLength; ++i) {data[i] = bitmap.buffer[i];}env->ReleaseByteArrayElements(buf, data, 0);env->SetObjectField(ft_bitmap, bufferID, buf);return 0;
}extern "C"
JNIEXPORT void JNICALL
Java_site_feiyuliuxing_jfreetype_JFreeType_close(JNIEnv *env, jobject thiz) {FT_Done_FreeType(library);
}

FTBitmap.kt

import android.graphics.Bitmap
import android.graphics.Colorclass FTBitmap @JvmOverloads constructor(var rows: Int = 0,var width: Int = 0,var buffer: ByteArray? = null,var bitmapLeft: Int = 0,var bitmapTop: Int = 0,
) {fun toBitmap(maxAscent: Int, maxDescent: Int): Bitmap? {if (buffer == null) return nullval xOffset = bitmapLeftval yOffset = maxAscent - bitmapTopval width = this.width + xOffsetval height = rows + yOffset + maxDescent - (rows - bitmapTop)val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)for (y in 0 until rows) {for (x in 0 until this.width) {val index = y * this.width + xval pixelValue = buffer!![index].toInt() and 0xffbitmap.setPixel(x + xOffset, y + yOffset, Color.rgb(pixelValue, pixelValue, pixelValue))}}return bitmap}
}

 JFreeType.kt

package site.feiyuliuxing.jfreetypeimport java.nio.ByteBufferclass JFreeType {/*** A native method that is implemented by the 'jfreetype' native library,* which is packaged with this application.*/external fun init(faceBuffer: ByteBuffer): Intexternal fun charBitmap(ftBitmap: FTBitmap, char: Char): Intexternal fun close()companion object {// Used to load the 'jfreetype' library on application startup.init {System.loadLibrary("jfreetype")}}
}

至此,我们需要的接口都已经准备好啦,继续~~

2、使用Open GL绘制文字

Android Open GL基础这里就不介绍了,如有需要,可以参考构建OpenGL ES环境

需要准备一个字体文件,可以自己搜索下载一个ttf,替换后面代码中的“SourceCodePro-Regular.ttf”

GLUtil.kt

import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.opengl.GLES11Ext.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT
import android.opengl.GLES11Ext.GL_TEXTURE_MAX_ANISOTROPY_EXT
import android.opengl.GLES30.*
import android.opengl.GLUtils
import android.util.Log
import androidx.annotation.DrawableRes
import java.nio.ByteBufferobject GLUtil {private const val TAG = "GLUtil"fun createShaderProgram(vertexShaderSource: String, fragmentShaderSource: String): Int {val vShader = glCreateShader(GL_VERTEX_SHADER)val fShader = glCreateShader(GL_FRAGMENT_SHADER)glShaderSource(vShader, vertexShaderSource)glShaderSource(fShader, fragmentShaderSource)val status = IntArray(1)glCompileShader(vShader)checkOpenGLError()glGetShaderiv(vShader, GL_COMPILE_STATUS, status, 0)if (status[0] != 1) {Log.e(TAG, "vertex compilation failed")printShaderLog(vShader)}glCompileShader(fShader)checkOpenGLError()glGetShaderiv(fShader, GL_COMPILE_STATUS, status, 0)if (status[0] != 1) {Log.e(TAG, "fragment compilation failed")printShaderLog(fShader)}val vfProgram = glCreateProgram()glAttachShader(vfProgram, vShader)glAttachShader(vfProgram, fShader)glLinkProgram(vfProgram)checkOpenGLError()glGetProgramiv(vfProgram, GL_LINK_STATUS, status, 0)if (status[0] != 1) {Log.e(TAG, "linking failed")printProgramLog(vfProgram)}return vfProgram}private fun printShaderLog(shader: Int) {val len = IntArray(1)glGetShaderiv(shader, GL_INFO_LOG_LENGTH, len, 0)if (len[0] > 0) {val log = glGetShaderInfoLog(shader)Log.e(TAG, "Shader Info Log: $log")}}private fun printProgramLog(prog: Int) {val len = IntArray(1)glGetProgramiv(prog, GL_INFO_LOG_LENGTH, len, 0)Log.e(TAG, "printProgramLog() - log length=${len[0]}")if (len[0] > 0) {val log = glGetProgramInfoLog(prog)Log.e(TAG, "Program Info Log: $log")}}private fun checkOpenGLError(): Boolean {var foundError = falsevar glErr = glGetError()while (glErr != GL_NO_ERROR) {Log.e(TAG, "glError: $glErr")foundError = trueglErr = glGetError()}return foundError}fun Context.loadTexture(@DrawableRes img: Int): Int {val options = BitmapFactory.Options()options.inScaled = falseval bitmap = BitmapFactory.decodeResource(resources, img, options)return loadTexture(bitmap)}fun loadTexture(bitmap: Bitmap): Int {Log.d(TAG, "bitmap size: ${bitmap.width} x ${bitmap.height}")val textures = IntArray(1)glGenTextures(1, textures, 0)val textureID = textures[0]if (textureID == 0) {Log.e(TAG, "Could not generate a new OpenGL textureId object.")return 0}glBindTexture(GL_TEXTURE_2D, textureID)// https://developer.android.google.cn/reference/android/opengl/GLES20#glTexImage2D(int,%20int,%20int,%20int,%20int,%20int,%20int,%20int,%20java.nio.Buffer)/*      int target,int level,int internalformat,int width,int height,int border,int format,int type,Buffer pixels */val pixels = ByteBuffer.allocateDirect(bitmap.byteCount)bitmap.copyPixelsToBuffer(pixels)pixels.position(0)//这步比较关键,不然无法加载纹理数据val internalformat = GLUtils.getInternalFormat(bitmap)val type = GLUtils.getType(bitmap)
//        Log.i(TAG, "internalformat=$internalformat, GL_RGBA=$GL_RGBA")
//        Log.i(TAG, "type=$type, GL_UNSIGNED_BYTE=$GL_UNSIGNED_BYTE")
//        glTexImage2D(GL_TEXTURE_2D, 0, internalformat, bitmap.width, bitmap.height, 0, internalformat, type, pixels)GLUtils.texImage2D(GL_TEXTURE_2D, 0, bitmap, 0)glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR)glGenerateMipmap(GL_TEXTURE_2D)val ext = glGetString(GL_EXTENSIONS)
//        Log.e(TAG, ext)if (ext.contains("GL_EXT_texture_filter_anisotropic")) {val anisoset = FloatArray(1)glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, anisoset, 0)Log.d(TAG, "anisoset=${anisoset[0]}")glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, anisoset[0])}bitmap.recycle()return textureID}
}

GLChar.kt

import android.graphics.Bitmap
import android.opengl.GLES30.*
import java.nio.ByteBuffer
import java.nio.ByteOrderclass GLChar(bitmap: Bitmap) {private var positionVertex = FloatArray(15)private val vertexBuffer = ByteBuffer.allocateDirect(positionVertex.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(positionVertex).apply{ position(0) }private val texVertexBuffer = ByteBuffer.allocateDirect(TEX_VERTEX.size * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(TEX_VERTEX).position(0)private val vertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.size * 2).order(ByteOrder.nativeOrder()).asShortBuffer().put(VERTEX_INDEX).position(0)private var textureId = 0var glWidth: Float = 0fprivate setvar glHeight: Float = 0fprivate setinit {textureId = GLUtil.loadTexture(bitmap)val cx = 0fval cy = 0fval xOffset = 0.0005f * bitmap.widthval yOffset = 0.0005f * bitmap.heightglWidth = xOffset * 2fglHeight = yOffset * 2fpositionVertex = floatArrayOf(cx, cy, 0f,xOffset, yOffset, 0f,-xOffset, yOffset, 0f,-xOffset, -yOffset, 0f,xOffset, -yOffset, 0f)vertexBuffer.position(0)vertexBuffer.put(positionVertex)vertexBuffer.position(0)}fun draw(vbo: IntArray) {glBindBuffer(GL_ARRAY_BUFFER, vbo[0])glBufferData(GL_ARRAY_BUFFER, vertexBuffer.capacity() * 4, vertexBuffer, GL_STATIC_DRAW)glVertexAttribPointer(0, 3, GL_FLOAT, false, 0, 0)glEnableVertexAttribArray(0)glBindBuffer(GL_ARRAY_BUFFER, vbo[1])glBufferData(GL_ARRAY_BUFFER, texVertexBuffer.capacity() * 4, texVertexBuffer, GL_STATIC_DRAW)glVertexAttribPointer(1, 2, GL_FLOAT, false, 0, 0)glEnableVertexAttribArray(1)//激活纹理glActiveTexture(GL_TEXTURE0)//绑定纹理glBindTexture(GL_TEXTURE_2D, textureId)// 绘制glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbo[2])glBufferData(GL_ELEMENT_ARRAY_BUFFER, VERTEX_INDEX.size * 2, vertexIndexBuffer, GL_STATIC_DRAW)glDrawElements(GL_TRIANGLES, VERTEX_INDEX.size, GL_UNSIGNED_SHORT, 0)}companion object {private const val TAG = "GLChar"/*** 绘制顺序索引*/private val VERTEX_INDEX = shortArrayOf(0, 1, 2,  //V0,V1,V2 三个顶点组成一个三角形0, 2, 3,  //V0,V2,V3 三个顶点组成一个三角形0, 3, 4,  //V0,V3,V4 三个顶点组成一个三角形0, 4, 1   //V0,V4,V1 三个顶点组成一个三角形)/*** 纹理坐标* (s,t)*/private val TEX_VERTEX = floatArrayOf(0.5f, 0.5f, //纹理坐标V01f, 0f,     //纹理坐标V10f, 0f,     //纹理坐标V20f, 1.0f,   //纹理坐标V31f, 1.0f    //纹理坐标V4)}
}

 GLText.tk

class GLText(text: String, glChars: Map<Char, GLChar>) {private val glCharList = mutableListOf<GLChar>()init {for (c in text) glChars[c]?.let(glCharList::add)}fun draw(vbo: IntArray, offsetBlock: (Float, Float)->Unit) {val textWidth = glCharList.sumOf { it.glWidth.toDouble() }.toFloat()var xOffset = -textWidth / 2ffor (glChar in glCharList) {offsetBlock(xOffset, 0f)glChar.draw(vbo)xOffset += glChar.glWidth}}
}

RendererText.kt

import android.content.Context
import android.opengl.GLES30.*
import android.opengl.GLSurfaceView
import android.opengl.Matrix
import java.nio.ByteBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10class RendererText(private val context: Context) : GLSurfaceView.Renderer, IShaderProvider {private val numVAOs = 1private val numVBOs = 3private val vao = IntArray(numVAOs)private val vbo = IntArray(numVBOs)private var cameraX = 0fprivate var cameraY = 0fprivate var cameraZ = 2.5fprivate var renderingProgram = 0private var mvLoc = 0private var projLoc = 0private val pMat = FloatArray(16)private val vMat = FloatArray(16)private val mMat = FloatArray(16)private val mvMat = FloatArray(16)private val glChars = mutableMapOf<Char, GLChar>()private var glText = GLText("", glChars)private fun loadGLChars() {val ft = JFreeType()val faceBuffer = context.assets.open("fonts/SourceCodePro-Regular.ttf").use {ByteBuffer.allocateDirect(it.available()).put(it.readBytes()).apply { position(0) }}ft.init(faceBuffer)val chars = mutableListOf<Char>()fun putChar(char: Char) {chars.add(char)}fun putChars(range: IntRange) {for (charcode in range) putChar(charcode.toChar())}putChars('A'.code..'Z'.code)putChars('a'.code..'z'.code)putChars('0'.code..'9'.code)putChar('!')val ftBitmaps = chars.map {val ftBitmap = FTBitmap()ft.charBitmap(ftBitmap, it)ftBitmap}var maxAscent = 0var maxDescent = 0for (ftBmp in ftBitmaps) {if (ftBmp.bitmapTop > maxAscent) maxAscent = ftBmp.bitmapTopif (ftBmp.rows - ftBmp.bitmapTop > maxDescent) maxDescent = ftBmp.rows - ftBmp.bitmapTop}for (i in chars.indices) {ftBitmaps[i].toBitmap(maxAscent, maxDescent)?.let { bitmap ->glChars[chars[i]] = GLChar(bitmap)}}ft.close()glText = GLText("HelloWorld!", glChars)}override fun onSurfaceCreated(p0: GL10?, p1: EGLConfig?) {renderingProgram = GLUtil.createShaderProgram(vertexShaderSource(), fragmentShaderSource())glUseProgram(renderingProgram)mvLoc = glGetUniformLocation(renderingProgram, "mv_matrix")projLoc = glGetUniformLocation(renderingProgram, "proj_matrix")glGenVertexArrays(1, vao, 0)glBindVertexArray(vao[0])glGenBuffers(numVBOs, vbo, 0)loadGLChars()}override fun onSurfaceChanged(p0: GL10?, width: Int, height: Int) {glViewport(0, 0, width, height)val aspect = width.toFloat() / height.toFloat()Matrix.perspectiveM(pMat, 0, Math.toDegrees(1.0472).toFloat(), aspect, 0.1f, 1000f)}override fun onDrawFrame(p0: GL10?) {glClearColor(0f, 0f, 0f, 1f)glClear(GL_COLOR_BUFFER_BIT)//下面两行代码,防止图片的透明部分被显示成黑色glEnable(GL_BLEND)glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)Matrix.setIdentityM(vMat, 0)Matrix.translateM(vMat, 0, -cameraX, -cameraY, -cameraZ)Matrix.setIdentityM(mMat, 0)Matrix.multiplyMM(mvMat, 0, vMat, 0, mMat, 0)glUniformMatrix4fv(mvLoc, 1, false, mvMat, 0)glUniformMatrix4fv(projLoc, 1, false, pMat, 0)glText.draw(vbo) { xOffset, yOffset ->Matrix.setIdentityM(mMat, 0)Matrix.translateM(mMat, 0, xOffset, yOffset, 0f)Matrix.multiplyMM(mvMat, 0, vMat, 0, mMat, 0)glUniformMatrix4fv(mvLoc, 1, false, mvMat, 0)}}override fun vertexShaderSource(): String {return """#version 300 eslayout (location = 0) in vec3 position;layout (location = 1) in vec2 tex_coord;out vec2 tc;uniform mat4 mv_matrix;uniform mat4 proj_matrix;uniform sampler2D s;void main(void){gl_Position = proj_matrix * mv_matrix * vec4(position, 1.0);tc = tex_coord;}""".trimIndent()}override fun fragmentShaderSource(): String {return """#version 300 esprecision mediump float;in vec2 tc;out vec4 color;uniform sampler2D s;void main(void){color = texture(s,tc);}""".trimIndent()}
}

效果图

3、总结

字符转位图,照着FreeType的文档很容易就实现了,其中关于字符水平对齐稍微花了点时间,后结合文档Managing Glyphs以及观察打印的数据,确定 bitmap_left 就是 bearingX,bitmap_top 是 bearingY,这样很容易把水平方向的字符按照 baseline 对齐。

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

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

相关文章

【学习心得】请求参数加密的原理与逆向思路

一、什么是请求参数加密&#xff1f; 请求参数加密是JS逆向反爬手段中的一种。它是指客户端&#xff08;浏览器&#xff09;执行JS代码&#xff0c;生成相应的加密参数。并带着加密后的参数请求服务器&#xff0c;得到正常的数据。 常见的被加密的请求参数sign 它的原理和过程图…

Sqli-labs靶场第19关详解[Sqli-labs-less-19]自动化注入-SQLmap工具注入

Sqli-labs-Less-19 通过测试发现&#xff0c;在登录界面没有注入点&#xff0c;通过已知账号密码admin&#xff0c;admin进行登录发现&#xff1a; 返回了Referer &#xff0c;设想如果在Referer 尝试加上注入语句&#xff08;报错注入&#xff09;&#xff0c;测试是否会执行…

C#,基于密度的噪声应用空间聚类算法(DBSCAN Algorithm)源代码

1 聚类算法 聚类分析或简单聚类基本上是一种无监督的学习方法&#xff0c;它将数据点划分为若干特定的批次或组&#xff0c;使得相同组中的数据点具有相似的属性&#xff0c;而不同组中的数据点在某种意义上具有不同的属性。它包括许多基于差分进化的不同方法。 E、 g.K-均值…

C#入门:简单数据类型和强制类型转换

本文由 简悦 SimpRead 转码&#xff0c; 原文地址 mp.weixin.qq.com 本期来讲讲 unity 的脚本语言 —C#&#xff0c;C# 的简单数据类型及范围和强制类型转化的方法。这可是 unity 游戏开发必备技能。 1. 简单数据类型 各个类型的范围&#xff1a; byte -> System.Byte (字节…

汽车碰撞与刮伤的实用维修技术,汽车的车身修复与涂装修补教学

一、教程描述 本套汽车维修技术教程&#xff0c;大小7.44G&#xff0c;共有60个文件。 二、教程目录 01-汽车车身修复教程01-安全规则&#xff08;共3课时&#xff09; 02-汽车车身修复教程02-汽车结构&#xff08;共3课时&#xff09; 03-汽车车身修复教程03-汽车修复所使…

贪心算法(区间问题)

452. 用最少数量的箭引爆气球 题目(求无重复区间) 有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points &#xff0c;其中points[i] [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。 一支弓箭可以沿着…

医学大数据|统计基础|医学统计学(笔记):开学说明与目录

开始学习统计基础&#xff0c;参考教材&#xff1a;医学统计学第五版 点点关注一切来学习吧 责任编辑&#xff1a;医学大数据刘刘老师&#xff1a;头部医疗大数据公司医学科学部研究员 邮箱&#xff1a;897282268qq.com 久菜盒子工作室 我们是&#xff1a;985硕博/美国全奖…

基于JavaWEB SpringBoot婚纱影楼摄影预约网站设计和实现

基于JavaWEB SSM SpringBoot婚纱影楼摄影预约网站设计和实现 博主介绍&#xff1a;多年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言…

python+Django+Neo4j中医药知识图谱与智能问答平台

文章目录 项目地址基础准备正式运行 项目地址 https://github.com/ZhChessOvO/ZeLanChao_KGQA 基础准备 请确保您的电脑有以下环境&#xff1a;python3&#xff0c;neo4j 在安装目录下进入cmd&#xff0c;输入指令“pip install -r requirement.txt”,安装需要的python库 打…

Network LSA 结构简述

Network LSA主要用于描述一个区域内的网络拓扑结构&#xff0c;包括网络中的路由器和连接到这些路由器的网络。它记录了每个路由器的邻居关系、连接状态以及连接的度量值&#xff08;如带宽、延迟等&#xff09;&#xff0c;以便计算最短路径和构建路由表。display ospf lsdb n…

Apache Flink连载(三十七):Flink基于Kubernetes部署(7)-Kubernetes 集群搭建-3

🏡 个人主页:IT贫道-CSDN博客 🚩 私聊博主:私聊博主加WX好友,获取更多资料哦~ 🔔 博主个人B栈地址:豹哥教你学编程的个人空间-豹哥教你学编程个人主页-哔哩哔哩视频 目录

【PyQt】16-剪切板的使用

文章目录 前言一、代码疑惑快捷键 二、现象2.1 复制粘贴文本复制粘贴 2.2 复制粘贴图片复制粘贴 2.3 复制粘贴网页 总结 前言 1、剪切板的使用 2、pycharm的编译快捷键 3、类的属性和普通变量的关系 4、pyqt应该养成的编程习惯-体现在代码里了&#xff0c;自己看看。 一、代码…