-
项目结构
app/
├── src/main/
│ ├── java/com/example/glimageviewer/
│ │ ├── GLImageRenderer.kt # OpenGL渲染器
│ │ ├── GLImageView.kt # 自定义GLSurfaceView
│ │ ├── ImageFormat.kt # 图像格式定义
│ │ ├── MainActivity.kt # 主Activity
│ │ └── utils/
│ │ ├── ImageUtils.kt # 图像处理工具
│ │ └── ShaderUtils.kt # 着色器工具
│ ├── res/
│ │ ├── layout/
│ │ │ ├── activity_main.xml # 主布局
│ │ │ └── layout_gl_image.xml # GL图像显示布局
│ │ └── values/
│ │ └── colors.xml
│ └── assets/
│ └── shaders/
│ ├── vertex_shader.glsl # 顶点着色器
│ └── fragment_shader.glsl # 片段着色器 -
核心代码实现
- 图像格式定义 (ImageFormat.kt)
package com.example.glimageviewer /** * 图像格式枚举 */ enum class ImageFormat(val value: Int) { GRAY(0), // 8位灰度图 RGB(1), // 24位RGB (R-G-B) BGR(2), // 24位BGR (B-G-R) YUV420(3), // YUV420格式 (Y平面 + U平面 + V平面) YUYV(4), // YUYV交错格式 NV21(5), // NV21格式 (Y平面 + VU交错) NV12(6); // NV12格式 (Y平面 + UV交错) companion object { fun fromValue(value: Int): ImageFormat { return values().find { it.value == value } ?: RGB } } } /** * 图像数据类 */ data class ImageData( val data: ByteBuffer, val width: Int, val height: Int, val format: ImageFormat, val timestamp: Long = System.currentTimeMillis() )
-
着色器工具类 (ShaderUtils.kt)
package com.example.glimageviewer.utils import android.opengl.GLES30 import android.util.Log object ShaderUtils { private const val TAG = "ShaderUtils" /** * 加载着色器 */ fun loadShader(type: Int, shaderCode: String): Int { val shader = GLES30.glCreateShader(type) GLES30.glShaderSource(shader, shaderCode) GLES30.glCompileShader(shader) // 检查编译状态 val compiled = IntArray(1) GLES30.glGetShaderiv(shader, GLES30.GL_COMPILE_STATUS, compiled, 0) if (compiled[0] == 0) { val error = GLES30.glGetShaderInfoLog(shader) Log.e(TAG, "Shader compilation failed: $error") GLES30.glDeleteShader(shader) return 0 } return shader } /** * 创建着色器程序 */ fun createProgram(vertexShaderCode: String, fragmentShaderCode: String): Int { val vertexShader = loadShader(GLES30.GL_VERTEX_SHADER, vertexShaderCode) val fragmentShader = loadShader(GLES30.GL_FRAGMENT_SHADER, fragmentShaderCode) if (vertexShader == 0 || fragmentShader == 0) { return 0 } val program = GLES30.glCreateProgram() GLES30.glAttachShader(program, vertexShader) GLES30.glAttachShader(program, fragmentShader) GLES30.glLinkProgram(program) // 检查链接状态 val linked = IntArray(1) GLES30.glGetProgramiv(program, GLES30.GL_LINK_STATUS, linked, 0) if (linked[0] == 0) { val error = GLES30.glGetProgramInfoLog(program) Log.e(TAG, "Program linking failed: $error") GLES30.glDeleteProgram(program) return 0 } // 清理着色器 GLES30.glDeleteShader(vertexShader) GLES30.glDeleteShader(fragmentShader) return program } }
-
图像处理工具类 (ImageUtils.kt)
package com.example.glimageviewer.utils import android.graphics.Bitmap import android.graphics.Color import java.nio.ByteBuffer object ImageUtils { /** * Bitmap转RGB ByteBuffer */ fun bitmapToRGB(bitmap: Bitmap): ByteBuffer { val rgbData = ByteArray(bitmap.width * bitmap.height * 3) var index = 0 for (y in 0 until bitmap.height) { for (x in 0 until bitmap.width) { val pixel = bitmap.getPixel(x, y) rgbData[index++] = Color.red(pixel).toByte() rgbData[index++] = Color.green(pixel).toByte() rgbData[index++] = Color.blue(pixel).toByte() } } val buffer = ByteBuffer.allocateDirect(rgbData.size) buffer.put(rgbData) buffer.position(0) return buffer } /** * Bitmap转BGR ByteBuffer */ fun bitmapToBGR(bitmap: Bitmap): ByteBuffer { val bgrData = ByteArray(bitmap.width * bitmap.height * 3) var index = 0 for (y in 0 until bitmap.height) { for (x in 0 until bitmap.width) { val pixel = bitmap.getPixel(x, y) bgrData[index++] = Color.blue(pixel).toByte() bgrData[index++] = Color.green(pixel).toByte() bgrData[index++] = Color.red(pixel).toByte() } } val buffer = ByteBuffer.allocateDirect(bgrData.size) buffer.put(bgrData) buffer.position(0) return buffer } /** * 创建测试图像数据 */ fun createTestImage(width: Int, height: Int, format: ImageFormat): ByteBuffer { return when (format) { ImageFormat.GRAY -> createGrayTestImage(width, height) ImageFormat.RGB -> createRGBTestImage(width, height) ImageFormat.BGR -> createBGRTestImage(width, height) ImageFormat.YUV420 -> createYUV420TestImage(width, height) ImageFormat.YUYV -> createYUYVTestImage(width, height) ImageFormat.NV21 -> createNV21TestImage(width, height) ImageFormat.NV12 -> createNV12TestImage(width, height) } } private fun createGrayTestImage(width: Int, height: Int): ByteBuffer { val data = ByteArray(width * height) for (y in 0 until height) { for (x in 0 until width) { val gray = ((x + y) * 255 / (width + height)).toByte() data[y * width + x] = gray } } val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.position(0) return buffer } private fun createRGBTestImage(width: Int, height: Int): ByteBuffer { val data = ByteArray(width * height * 3) var index = 0 for (y in 0 until height) { for (x in 0 until width) { data[index++] = (x * 255 / width).toByte() // R data[index++] = (y * 255 / height).toByte() // G data[index++] = 128.toByte() // B } } val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.position(0) return buffer } private fun createBGRTestImage(width: Int, height: Int): ByteBuffer { val data = ByteArray(width * height * 3) var index = 0 for (y in 0 until height) { for (x in 0 until width) { data[index++] = 128.toByte() // B data[index++] = (y * 255 / height).toByte() // G data[index++] = (x * 255 / width).toByte() // R } } val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.position(0) return buffer } private fun createYUV420TestImage(width: Int, height: Int): ByteBuffer { val ySize = width * height val uvSize = width * height / 4 val data = ByteArray(ySize + uvSize * 2) // Y分量 for (y in 0 until height) { for (x in 0 until width) { val yValue = ((x + y) * 255 / (width + height)).toByte() data[y * width + x] = yValue } } // U分量 for (i in 0 until uvSize) { data[ySize + i] = 128.toByte() } // V分量 for (i in 0 until uvSize) { data[ySize + uvSize + i] = 128.toByte() } val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.position(0) return buffer } private fun createYUYVTestImage(width: Int, height: Int): ByteBuffer { val data = ByteArray(width * height * 2) var index = 0 for (y in 0 until height) { for (x in 0 until width step 2) { data[index++] = (x * 255 / width).toByte() // Y1 data[index++] = 128.toByte() // U data[index++] = (y * 255 / height).toByte() // Y2 data[index++] = 128.toByte() // V } } val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.position(0) return buffer } private fun createNV21TestImage(width: Int, height: Int): ByteBuffer { val ySize = width * height val vuSize = width * height / 2 val data = ByteArray(ySize + vuSize) // Y分量 for (y in 0 until height) { for (x in 0 until width) { val yValue = ((x + y) * 255 / (width + height)).toByte() data[y * width + x] = yValue } } // VU分量 for (i in 0 until vuSize step 2) { data[ySize + i] = 128.toByte() // V data[ySize + i + 1] = 128.toByte() // U } val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.position(0) return buffer } private fun createNV12TestImage(width: Int, height: Int): ByteBuffer { val ySize = width * height val uvSize = width * height / 2 val data = ByteArray(ySize + uvSize) // Y分量 for (y in 0 until height) { for (x in 0 until width) { val yValue = ((x + y) * 255 / (width + height)).toByte() data[y * width + x] = yValue } } // UV分量 for (i in 0 until uvSize step 2) { data[ySize + i] = 128.toByte() // U data[ySize + i + 1] = 128.toByte() // V } val buffer = ByteBuffer.allocateDirect(data.size) buffer.put(data) buffer.position(0) return buffer } }
-
OpenGL渲染器 (GLImageRenderer.kt)
package com.example.glimageviewer import android.opengl.GLES30 import android.opengl.GLSurfaceView import android.opengl.Matrix import android.util.Log import com.example.glimageviewer.utils.ShaderUtils import java.nio.ByteBuffer import java.nio.ByteOrder import java.nio.FloatBuffer import javax.microedition.khronos.egl.EGLConfig import javax.microedition.khronos.opengles.GL10 /** * OpenGL ES 3.0 图像渲染器 * 支持多种图像格式:GRAY, RGB, BGR, YUV420, YUYV, NV21, NV12 */ class GLImageRenderer : GLSurfaceView.Renderer { companion object { private const val TAG = "GLImageRenderer" // 顶点坐标 (全屏四边形) private val VERTEX_COORDS = floatArrayOf( -1.0f, 1.0f, 0.0f, // 左上 -1.0f, -1.0f, 0.0f, // 左下 1.0f, 1.0f, 0.0f, // 右上 1.0f, -1.0f, 0.0f // 右下 ) // 纹理坐标 private val TEX_COORDS = floatArrayOf( 0.0f, 0.0f, // 左下 0.0f, 1.0f, // 左上 1.0f, 0.0f, // 右下 1.0f, 1.0f // 右上 ) } // OpenGL对象 private var program = 0 private val textures = IntArray(3) // 最多3个纹理 (YUV需要3个) private var vertexBuffer: FloatBuffer? = null private var texCoordBuffer: FloatBuffer? = null // Uniform位置 private var uMVPMatrixLocation = 0 private var uFormatLocation = 0 private var uTexture0Location = 0 private var uTexture1Location = 0 private var uTexture2Location = 0 // 图像数据 private var imageData: ImageData? = null private val mvpMatrix = FloatArray(16) // 线程安全 private val lock = Object() init { initBuffers() } private fun initBuffers() { // 初始化顶点缓冲区 vertexBuffer = ByteBuffer.allocateDirect(VERTEX_COORDS.size * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer() vertexBuffer?.put(VERTEX_COORDS)?.position(0) // 初始化纹理坐标缓冲区 texCoordBuffer = ByteBuffer.allocateDirect(TEX_COORDS.size * 4) .order(ByteOrder.nativeOrder()) .asFloatBuffer() texCoordBuffer?.put(TEX_COORDS)?.position(0) } override fun onSurfaceCreated(gl: GL10?, config: EGLConfig?) { Log.d(TAG, "onSurfaceCreated") // 设置背景色 GLES30.glClearColor(0.0f, 0.0f, 0.0f, 1.0f) // 创建着色器程序 program = ShaderUtils.createProgram(VERTEX_SHADER_CODE, FRAGMENT_SHADER_CODE) if (program == 0) { Log.e(TAG, "Failed to create shader program") return } // 获取Uniform位置 uMVPMatrixLocation = GLES30.glGetUniformLocation(program, "uMVPMatrix") uFormatLocation = GLES30.glGetUniformLocation(program, "uFormat") uTexture0Location = GLES30.glGetUniformLocation(program, "uTexture0") uTexture1Location = GLES30.glGetUniformLocation(program, "uTexture1") uTexture2Location = GLES30.glGetUniformLocation(program, "uTexture2") // 设置纹理单元 GLES30.glUseProgram(program) GLES30.glUniform1i(uTexture0Location, 0) GLES30.glUniform1i(uTexture1Location, 1) GLES30.glUniform1i(uTexture2Location, 2) // 创建纹理 GLES30.glGenTextures(3, textures, 0) for (i in 0..2) { GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[i]) GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_LINEAR) GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR) GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE) GLES30.glTexParameteri(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE) } // 初始化矩阵 Matrix.setIdentityM(mvpMatrix, 0) } override fun onSurfaceChanged(gl: GL10?, width: Int, height: Int) { Log.d(TAG, "onSurfaceChanged: ${width}x${height}") GLES30.glViewport(0, 0, width, height) // 计算宽高比 val ratio = width.toFloat() / height.toFloat() // 设置正交投影矩阵 if (width > height) { Matrix.orthoM(mvpMatrix, 0, -ratio, ratio, -1f, 1f, -1f, 1f) } else { Matrix.orthoM(mvpMatrix, 0, -1f, 1f, -1f / ratio, 1f / ratio, -1f, 1f) } } override fun onDrawFrame(gl: GL10?) { GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT) if (program == 0) return GLES30.glUseProgram(program) // 传递MVP矩阵 GLES30.glUniformMatrix4fv(uMVPMatrixLocation, 1, false, mvpMatrix, 0) // 获取当前图像数据 val currentImageData = synchronized(lock) { imageData } if (currentImageData != null) { // 传递格式 GLES30.glUniform1i(uFormatLocation, currentImageData.format.value) // 绑定纹理数据 bindTextureData(currentImageData) // 绑定顶点坐标 val aPosition = GLES30.glGetAttribLocation(program, "aPosition") GLES30.glEnableVertexAttribArray(aPosition) GLES30.glVertexAttribPointer(aPosition, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer) // 绑定纹理坐标 val aTexCoord = GLES30.glGetAttribLocation(program, "aTexCoord") GLES30.glEnableVertexAttribArray(aTexCoord) GLES30.glVertexAttribPointer(aTexCoord, 2, GLES30.GL_FLOAT, false, 0, texCoordBuffer) // 绘制 GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 4) // 禁用顶点数组 GLES30.glDisableVertexAttribArray(aPosition) GLES30.glDisableVertexAttribArray(aTexCoord) } } /** * 更新图像数据 */ fun updateImage(imageData: ImageData) { synchronized(lock) { this.imageData = imageData } } /** * 根据格式绑定纹理数据 */ private fun bindTextureData(imageData: ImageData) { when (imageData.format) { ImageFormat.GRAY -> bindGrayTexture(imageData) ImageFormat.RGB -> bindRGBTexture(imageData) ImageFormat.BGR -> bindBGRTexture(imageData) ImageFormat.YUV420 -> bindYUV420Texture(imageData) ImageFormat.YUYV -> bindYUYVTexture(imageData) ImageFormat.NV21 -> bindNV21Texture(imageData) ImageFormat.NV12 -> bindNV12Texture(imageData) } } private fun bindGrayTexture(imageData: ImageData) { GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE, imageData.width, imageData.height, 0, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, imageData.data ) } private fun bindRGBTexture(imageData: ImageData) { GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGB, imageData.width, imageData.height, 0, GLES30.GL_RGB, GLES30.GL_UNSIGNED_BYTE, imageData.data ) } private fun bindBGRTexture(imageData: ImageData) { GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGB, imageData.width, imageData.height, 0, GLES30.GL_RGB, GLES30.GL_UNSIGNED_BYTE, imageData.data ) } private fun bindYUV420Texture(imageData: ImageData) { val ySize = imageData.width * imageData.height val uvSize = ySize / 4 // Y分量 GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]) imageData.data.position(0) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE, imageData.width, imageData.height, 0, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, imageData.data ) // U分量 val uBuffer = ByteBuffer.allocateDirect(uvSize) imageData.data.position(ySize) for (i in 0 until uvSize) { uBuffer.put(imageData.data.get()) } uBuffer.position(0) GLES30.glActiveTexture(GLES30.GL_TEXTURE1) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[1]) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE, imageData.width / 2, imageData.height / 2, 0, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, uBuffer ) // V分量 val vBuffer = ByteBuffer.allocateDirect(uvSize) imageData.data.position(ySize + uvSize) for (i in 0 until uvSize) { vBuffer.put(imageData.data.get()) } vBuffer.position(0) GLES30.glActiveTexture(GLES30.GL_TEXTURE2) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[2]) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE, imageData.width / 2, imageData.height / 2, 0, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, vBuffer ) } private fun bindYUYVTexture(imageData: ImageData) { GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]) imageData.data.position(0) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, imageData.width / 2, imageData.height, 0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, imageData.data ) } private fun bindNV21Texture(imageData: ImageData) { val ySize = imageData.width * imageData.height val vuSize = ySize / 2 // Y分量 GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]) imageData.data.position(0) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE, imageData.width, imageData.height, 0, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, imageData.data ) // VU分量 val vuBuffer = ByteBuffer.allocateDirect(vuSize) imageData.data.position(ySize) for (i in 0 until vuSize) { vuBuffer.put(imageData.data.get()) } vuBuffer.position(0) GLES30.glActiveTexture(GLES30.GL_TEXTURE1) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[1]) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE_ALPHA, imageData.width / 2, imageData.height / 2, 0, GLES30.GL_LUMINANCE_ALPHA, GLES30.GL_UNSIGNED_BYTE, vuBuffer ) } private fun bindNV12Texture(imageData: ImageData) { val ySize = imageData.width * imageData.height val uvSize = ySize / 2 // Y分量 GLES30.glActiveTexture(GLES30.GL_TEXTURE0) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]) imageData.data.position(0) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE, imageData.width, imageData.height, 0, GLES30.GL_LUMINANCE, GLES30.GL_UNSIGNED_BYTE, imageData.data ) // UV分量 val uvBuffer = ByteBuffer.allocateDirect(uvSize) imageData.data.position(ySize) for (i in 0 until uvSize) { uvBuffer.put(imageData.data.get()) } uvBuffer.position(0) GLES30.glActiveTexture(GLES30.GL_TEXTURE1) GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[1]) GLES30.glTexImage2D( GLES30.GL_TEXTURE_2D, 0, GLES30.GL_LUMINANCE_ALPHA, imageData.width / 2, imageData.height / 2, 0, GLES30.GL_LUMINANCE_ALPHA, GLES30.GL_UNSIGNED_BYTE, uvBuffer ) } /** * 释放资源 */ fun release() { if (program != 0) { GLES30.glDeleteProgram(program) program = 0 } GLES30.glDeleteTextures(3, textures, 0) } companion object { // 顶点着色器 private val VERTEX_SHADER_CODE = "#version 300 es\n" + "uniform mat4 uMVPMatrix;\n" + "layout(location=0) in vec4 aPosition;\n" + "layout(location=1) in vec2 aTexCoord;\n" + "out vec2 vTexCoord;\n" + "\n" + "void main() {\n" + " gl_Position = uMVPMatrix * aPosition;\n" + " vTexCoord = aTexCoord;\n" + "}" // 片段着色器 private val FRAGMENT_SHADER_CODE = "#version 300 es\n" + "precision mediump float;\n" + "\n" + "uniform int uFormat;\n" + "uniform sampler2D uTexture0;\n" + "uniform sampler2D uTexture1;\n" + "uniform sampler2D uTexture2;\n" + "\n" + "in vec2 vTexCoord;\n" + "out vec4 outColor;\n" + "\n" + "// 格式常量\n" + "#define FORMAT_GRAY 0\n" + "#define FORMAT_YUYV 1\n" + "#define FORMAT_YUV420 2\n" + "#define FORMAT_BGR 3\n" + " \n" + "\n" + "\n" + "// YUV转RGB转换函数 (BT.601标准)\n" + "vec4 yuvToRgb(float y, float u, float v) {\n" + " y = 1.1643 * (y - 0.0625);\n" + " u = u - 0.5;\n" + " v = v - 0.5;\n" + " \n" + " float r = clamp(y + 1.5958 * v, 0.0, 1.0);\n" + " float g = clamp(y - 0.39173 * u - 0.81290 * v, 0.0, 1.0);\n" + " float b = clamp(y + 2.017 * u, 0.0, 1.0);\n" + " \n" + " return vec4(r, g, b, 1.0);\n" + "}\n" + "\n" + "// 处理YUYV格式\n" + "vec4 processYUYV() {\n" + " vec2 realCoord = vec2(vTexCoord.x * 2.0, vTexCoord.y);\n" + " vec4 yuyv = texture(uTexture0, vec2(realCoord.x * 0.5, realCoord.y));\n" + " \n" + " float y, u, v;\n" + " if (fract(realCoord.x) < 0.5) {\n" + " y = yuyv.r;\n" + " u = yuyv.g;\n" + " v = yuyv.a;\n" + " } else {\n" + " y = yuyv.b;\n" + " u = yuyv.g;\n" + " v = yuyv.a;\n" + " }\n" + " \n" + " return yuvToRgb(y, u, v);\n" + "}\n" + "\n" + "void main() {\n" + " switch(uFormat) {\n" + " case FORMAT_GRAY:\n" + " float gray = texture(uTexture0, vTexCoord).r;\n" + " outColor = vec4(gray, gray, gray, 1.0);\n" + " break;\n" + " \n" + " case FORMAT_YUYV:\n" + " outColor = processYUYV();\n" + " break;\n" + " \n" + " case FORMAT_YUV420:\n" + " float y = texture(uTexture0, vTexCoord).r;\n" + " float u = texture(uTexture1, vTexCoord).r;\n" + " float v = texture(uTexture2, vTexCoord).r;\n" + " outColor = yuvToRgb(y, u, v);\n" + " break; \n" + " \n" + " case FORMAT_BGR:\n" + " vec4 rgbColor = texture(uTexture0, vTexCoord);\n" + " outColor = vec4(rgbColor.b, rgbColor.g, rgbColor.r, 1.0);\n" + " break;\n" + " \n" + " default:\n" + " outColor = vec4(1.0, 0.0, 0.0, 1.0);\n" + " }\n" + "}" } }
-
自定义GLSurfaceView (GLImageView.kt)
package com.example.glimageviewer import android.content.Context import android.graphics.PixelFormat import android.opengl.GLSurfaceView import android.util.AttributeSet import android.util.Log /** * 自定义GLSurfaceView用于图像显示 */ class GLImageView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : GLSurfaceView(context, attrs, defStyleAttr) { companion object { private const val TAG = "GLImageView" } private var renderer: GLImageRenderer? = null init { initGLSurfaceView() } private fun initGLSurfaceView() { // 设置OpenGL ES 3.0 setEGLContextClientVersion(3) // 设置EGL配置 setEGLConfigChooser(8, 8, 8, 8, 16, 0) // 设置像素格式 holder.setFormat(PixelFormat.TRANSLUCENT) // 创建渲染器 renderer = GLImageRenderer() setRenderer(renderer) // 设置渲染模式为按需渲染 renderMode = RENDERMODE_WHEN_DIRTY Log.d(TAG, "GLImageView initialized") } /** * 更新图像数据 */ fun updateImage(imageData: ImageData) { renderer?.updateImage(imageData) requestRender() } /** * 更新图像数据 (便捷方法) */ fun updateImage(data: ByteBuffer, width: Int, height: Int, format: ImageFormat) { val imageData = ImageData(data, width, height, format) updateImage(imageData) } /** * 释放资源 */ fun release() { renderer?.release() renderer = null } override fun onDetachedFromWindow() { release() super.onDetachedFromWindow() } }
-
主Activity (MainActivity.kt)
package com.example.glimageviewer import android.os.Bundle import android.util.Log import android.widget.Button import android.widget.Spinner import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import com.example.glimageviewer.utils.ImageUtils import java.nio.ByteBuffer class MainActivity : AppCompatActivity() { companion object { private const val TAG = "MainActivity" private const val TEST_WIDTH = 640 private const val TEST_HEIGHT = 480 } private lateinit var glImageView: GLImageView private lateinit var formatSpinner: Spinner private lateinit var updateButton: Button private lateinit var infoTextView: TextView override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) initViews() setupListeners() } private fun initViews() { glImageView = findViewById(R.id.glImageView) formatSpinner = findViewById(R.id.formatSpinner) updateButton = findViewById(R.id.updateButton) infoTextView = findViewById(R.id.infoTextView) // 显示初始信息 updateInfo("GLSurfaceView图像显示器已初始化") } private fun setupListeners() { updateButton.setOnClickListener { updateTestImage() } } private fun updateTestImage() { val selectedFormat = when (formatSpinner.selectedItemPosition) { 0 -> ImageFormat.GRAY 1 -> ImageFormat.RGB 2 -> ImageFormat.BGR 3 -> ImageFormat.YUV420 4 -> ImageFormat.YUYV 5 -> ImageFormat.NV21 6 -> ImageFormat.NV12 else -> ImageFormat.RGB } try { val testData = ImageUtils.createTestImage(TEST_WIDTH, TEST_HEIGHT, selectedFormat) glImageView.updateImage(testData, TEST_WIDTH, TEST_HEIGHT, selectedFormat) val info = "格式: ${selectedFormat.name}, 尺寸: ${TEST_WIDTH}x${TEST_HEIGHT}, 时间: ${System.currentTimeMillis()}" updateInfo(info) Log.d(TAG, "Updated image: $info") } catch (e: Exception) { Log.e(TAG, "Failed to update image", e) updateInfo("更新图像失败: ${e.message}") } } private fun updateInfo(info: String) { infoTextView.text = info } override fun onDestroy() { glImageView.release() super.onDestroy() } }