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.mzph.cn/news/724010.shtml

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

相关文章

部署LVS负载均衡架构

目录 一、ipvsadm 工具 二、NAT模式下部署LVS负载均衡 1、部署NFS共享存储服务器 1.1 安装NFS软件 1.2 新建共享目录和站点文件 1.3 设置共享策略 2、部署节点服务器1 2.1 安装并启动nginx软件 2.2 挂载共享目录到网页站点目录 2.3 修改网关 3、部署节点服务器2 3.…

在ABAP中创建一个简单的守护进程

原文地址&#xff1a;Create a simple Daemon in ABAP 目录 一、ABAP语言中的守护进程是什么&#xff1f;二、ABAP 守护进程框架 (ADF)三、ABAP 守护进程类四、创建一个简单的ABAP守护进程步骤1&#xff1a;创建一个新的ABAP Daemon类步骤2&#xff1a;实现ON_ACCEPT方法第三步…

「滚雪球学Java」:GUI编程(章节汇总)

咦咦咦&#xff0c;各位小可爱&#xff0c;我是你们的好伙伴——bug菌&#xff0c;今天又来给大家普及Java SE相关知识点了&#xff0c;别躲起来啊&#xff0c;听我讲干货还不快点赞&#xff0c;赞多了我就有动力讲得更嗨啦&#xff01;所以呀&#xff0c;养成先点赞后阅读的好…

Kosmos-1: 通用接口架构下的多模态大语言模型

Kosmos-1: 通用接口架构下的多模态大语言模型 FesianXu 20230513 at Baidu Search Team 前言 在大规模语言模型&#xff08;Large Language Model, LLM&#xff09;看似要带来新一番人工智能变革浪潮之际&#xff0c;越来越多尝试以LLM作为通用接口去融入各种任务的工作&#…

【vue】ant-design弹出框无法关闭和runtimecore提示isFucntion is not function的问题修复

【vue】ant-design弹出框无法关闭和runtimecore提示isFucntion is not function的问题修复&#xff0c;初步分析是vue发布3.4版本以后引起的兼容性问题。 问题截图&#xff1a; 1.isFucntion is not function&#xff0c;是由于vue升级后众多插件版本不匹配造成的问题 2.弹框…

计算机中msvcp140.dll,丢失怎么修复与解决

一、msvcp140.dll20个软件环境 msvcp140.dll文件是许多软件运行环境的组成部分&#xff0c;通常与Microsoft Visual C Redistributable关联。以下是可能使用该文件的软件环境&#xff1a; 微软办公软件&#xff1a;如Microsoft Office套件&#xff0c;包括Word、Excel、Power…

Python给图片加水印

受到“手动给证件加文字太麻烦”的感触&#xff0c;想用Python来实现给图片加水印&#xff0c;这不方便多了。 这里使用PIL模块&#xff1a; from PIL import Image from PIL import ImageFont from PIL import ImageDrawimg_t Image.open(cat.jpg) img_size_t img_t.size…

OJ:循环队列

622. 设计循环队列 - 力扣&#xff08;LeetCode&#xff09; 思路 思路&#xff1a;首先循环队列的意思是&#xff1a;空间固定&#xff0c;就是提前开辟好&#xff0c;满了就不能插入了&#xff0c;但是删除数据后仍有空间&#xff0c;删除循环队列里面的数据后&#xff0c;保…

Apache ECharts数据可视化技术

介绍 官方地址:Apache ECharts 快速入门案例echarts.init //初始化方法 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>ECharts</title><!-- 引入刚刚下载的 ECharts 文件 --><script src"echart…

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

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

Java Swing游戏开发学习8

内容来自RyiSnow视频讲解 上一节提到的bug&#xff0c;不知道有没有人发现&#xff1f; 在播放音乐和音效的时候使用的是同一个clip对象&#xff0c;播放背景音乐在前&#xff0c;后续播放音效&#xff0c;clip对象就被覆盖了&#xff0c;因此导致调用停止播放背景音乐的时候&a…

计算机组成原理之机器:总线

计算机组成原理之机器 笔记来源&#xff1a;哈尔滨工业大学计算机组成原理&#xff08;哈工大刘宏伟&#xff09; Chapter2&#xff1a;总线 2.1 总线的基本概念 1.为什么需要总线&#xff1f;有几百个部件需要连接进行信息传输 2.什么是总线&#xff1f;总线是连接各个部件…

七、链表问题(上)

160、相交链表&#xff08;简单&#xff09; 题目描述 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点&#xff0c;返回 null 。 图示两个链表在节点 c1 开始相交&#xff1a; 题目数据 保证 整个…

如何选择阿里云服务器配置?(CPU/内存/带宽/磁盘)

阿里云服务器配置怎么选择&#xff1f;CPU内存、公网带宽和系统盘怎么选择&#xff1f;个人开发者或中小企业选择轻量应用服务器、ECS经济型e实例&#xff0c;企业用户选择ECS通用算力型u1云服务器、ECS计算型c7、通用型g7云服务器&#xff0c;阿里云服务器网aliyunfuwuqi.com整…

对象得定义与使用(动力节点老杜)

对象思想 1.什么是面向过程&#xff0c;什么是面向对象&#xff1f; 换而言之&#xff0c;面向对象思想实际就是将整体分成一个个独立的单元&#xff0c;每个单元都有自己得任务和属性&#xff0c;所有单元结合在一起完成一个整体。如果某个单元出现了问题还可以及时处理&…

比肩Gen-2,全新开源文生视频模型

著名开源平台Stability.ai在官网宣布&#xff0c;推出全新文生视频的扩散模型Stable Video Diffusion&#xff0c;已开源了该项目并公布了论文。 据悉&#xff0c;用户通过文本或图像就能生成高精准&#xff0c;14帧和25帧的短视频。目前&#xff0c;Stable Video Diffusion处…

回溯算法02-组合总合III(Java)

2.组合总合III 题目描述 找出所有相加之和为 n 的 k 个数的组合&#xff0c;且满足下列条件&#xff1a; 只使用数字1到9每个数字 最多使用一次 返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次&#xff0c;组合可以以任何顺序返回。 示例 1: 输入: k 3,…

[Linux]如何理解kernel、shell、bash

文章目录 概念总览kernelshell&bash 概念总览 内核(kernel) &#xff0c;外壳(shell) &#xff0c;bash kernel kernel是指操作系统中的核心部分&#xff0c;用户一般是不能直接使用kernel的。它主要负责管理硬件资源和提供系统服务&#xff0c;如内存管理、进程管理、文件…

Pytest测试技巧之Fixture:模块化管理测试数据!

在 Pytest 测试中&#xff0c;有效管理测试数据是提高测试质量和可维护性的关键。本文将深入探讨 Pytest 中的 Fixture&#xff0c;特别是如何利用 Fixture 实现测试数据的模块化管理&#xff0c;以提高测试用例的清晰度和可复用性。 什么是Fixture&#xff1f; 在 Pytest 中…

【考研数学】武忠祥各阶段用书搭配+学习包

25考研数学全流程规划&#xff01;别等到二战了才知道这样学 本人属于基础很差相当于是零基础的考研党&#xff0c;经过一年备考成功上岸 中间花费了很多时间在考研数学备考信息检索上&#xff0c;写下这篇希望能帮助基础不好的学弟学妹们多节约一些时间复习&#xff01; 25…