Android使用OpenGL和FreeType绘制文字

2024-03-03 09:52

本文主要是介绍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);
}

 JFreeType.java

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

package site.feiyuliuxing.gl_commimport 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 site.feiyuliuxing.gl_comm.GLUtil
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 site.feiyuliuxing.gl_comm.GLUtil
import site.feiyuliuxing.gl_comm.IShaderProvider
import site.feiyuliuxing.jfreetype.FTBitmap
import site.feiyuliuxing.jfreetype.JFreeType
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 对齐。

这篇关于Android使用OpenGL和FreeType绘制文字的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

使用Python开发一个简单的本地图片服务器

《使用Python开发一个简单的本地图片服务器》本文介绍了如何结合wxPython构建的图形用户界面GUI和Python内建的Web服务器功能,在本地网络中搭建一个私人的,即开即用的网页相册,文中的示... 目录项目目标核心技术栈代码深度解析完整代码工作流程主要功能与优势潜在改进与思考运行结果总结你是否曾经

Linux中的计划任务(crontab)使用方式

《Linux中的计划任务(crontab)使用方式》:本文主要介绍Linux中的计划任务(crontab)使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、前言1、linux的起源与发展2、什么是计划任务(crontab)二、crontab基础1、cro

kotlin中const 和val的区别及使用场景分析

《kotlin中const和val的区别及使用场景分析》在Kotlin中,const和val都是用来声明常量的,但它们的使用场景和功能有所不同,下面给大家介绍kotlin中const和val的区别,... 目录kotlin中const 和val的区别1. val:2. const:二 代码示例1 Java

C++变换迭代器使用方法小结

《C++变换迭代器使用方法小结》本文主要介绍了C++变换迭代器使用方法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1、源码2、代码解析代码解析:transform_iterator1. transform_iterat

C++中std::distance使用方法示例

《C++中std::distance使用方法示例》std::distance是C++标准库中的一个函数,用于计算两个迭代器之间的距离,本文主要介绍了C++中std::distance使用方法示例,具... 目录语法使用方式解释示例输出:其他说明:总结std::distance&n编程bsp;是 C++ 标准

vue使用docxtemplater导出word

《vue使用docxtemplater导出word》docxtemplater是一种邮件合并工具,以编程方式使用并处理条件、循环,并且可以扩展以插入任何内容,下面我们来看看如何使用docxtempl... 目录docxtemplatervue使用docxtemplater导出word安装常用语法 封装导出方

Linux换行符的使用方法详解

《Linux换行符的使用方法详解》本文介绍了Linux中常用的换行符LF及其在文件中的表示,展示了如何使用sed命令替换换行符,并列举了与换行符处理相关的Linux命令,通过代码讲解的非常详细,需要的... 目录简介检测文件中的换行符使用 cat -A 查看换行符使用 od -c 检查字符换行符格式转换将

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.