Android OpenGL ES 离屏幕渲染2——获取渲染结果并显示到ImageView控件中,使用最简模型展示

简介:

        紧接上文,本文将用一个不包含顶点shader和片元shader的最小模型讲述如何把通过EGL创建的OpenGL ES环境渲染后的结果进行提取,单纯输出一片铺满视口的红色的像素。

EGL环境创建逻辑:

        先看完整代码:

package com.cjztest.glOffscreenProcess.demo0

import android.content.Context
import android.opengl.EGL14
import android.opengl.EGLConfig
import android.opengl.EGLContext
import android.opengl.EGLDisplay
import android.opengl.EGLSurface
import android.util.DisplayMetrics
import android.view.WindowManager

/**以指定的大小产生EGLContext、EGLDisplay对象,并把OpenGL ES渲染管线的内容输出到EGLDisplay中*/
class EGLMaker : Object {
    protected var mEGLDisplay: EGLDisplay? = null
    protected var mEGLConfig: EGLConfig? = null
    protected var mEGLContext: EGLContext? = null
    protected var mEGLSurface: EGLSurface? = null
    protected var mEglStatus: EglStatus = EglStatus.INVALID
    protected var mContext: Context? = null
    private var mRenderer: IRenderer? = null
    protected var mWidth: Int = 0
    protected var mHeight: Int = 0
    var mIsCreated = false

    // EGLConfig参数
    private val mEGLConfigAttrs = intArrayOf(
            EGL14.EGL_RED_SIZE, 8,
            EGL14.EGL_GREEN_SIZE, 8,
            EGL14.EGL_BLUE_SIZE, 8,
            EGL14.EGL_ALPHA_SIZE, 8,
            EGL14.EGL_DEPTH_SIZE, 8,
            EGL14.EGL_RENDERABLE_TYPE,
            EGL14.EGL_OPENGL_ES2_BIT,
            EGL14.EGL_NONE
    )

    enum class EglStatus {
        INVALID, INITIALIZED, CREATED, CHANGED, DRAW;
        val isValid: Boolean
            get() = this != INVALID
    }

    // 渲染器接口
    interface IRenderer {
        fun onSurfaceCreated()
        fun onSurfaceChanged(width: Int, height: Int)
        fun onDrawFrame()
    }

    fun setRenderer(renderer: IRenderer) {
        mRenderer = renderer
    }

    // EGLContext参数
    private val mEGLContextAttrs = intArrayOf(EGL14.EGL_CONTEXT_CLIENT_VERSION, 2, EGL14.EGL_NONE)

    /**以当前window宽高创建EGLDisplay**/
    constructor(context: Context) {
        mContext = context
        val mWindowManager = mContext!!.getSystemService(Context.WINDOW_SERVICE) as WindowManager
        val displayMetrics = DisplayMetrics()
        mWindowManager.defaultDisplay.getRealMetrics(displayMetrics)
        mWidth = displayMetrics.widthPixels
        mHeight = displayMetrics.heightPixels
        createEGLEnv()
    }

   
    /** 以指定宽高创建EGLDisplay**/
    constructor(context: Context, width: Int, height: Int) {
        mContext = context
        mWidth = width
        mHeight = height
        createEGLEnv()
    }

    // 创建EGL环境
    fun createEGLEnv() : Boolean {
        if (mIsCreated) {
            return true
        }
        //1.创建EGLDisplay
        mEGLDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY)
        val versions = IntArray(2)
        EGL14.eglInitialize(mEGLDisplay, versions, 0, versions, 1)
        // 2.创建EGLConfig
        val configs: Array<EGLConfig?> = arrayOfNulls(1)
        val configNum = IntArray(1)
        EGL14.eglChooseConfig(mEGLDisplay, mEGLConfigAttrs, 0, configs, 0, 1, configNum, 0) //获取1个EGL配置,因为这个例子只需要一个
        if (configNum[0] > 0) {
            mEGLConfig = configs[0]
        } else {
            return false
        }
        // 3.创建EGLContext
        if (mEGLConfig != null) {
            mEGLContext = EGL14.eglCreateContext(mEGLDisplay, mEGLConfig, EGL14.EGL_NO_CONTEXT, mEGLContextAttrs, 0)
        }

        // 4.创建EGLSurface
        if (mEGLContext != null && mEGLContext != EGL14.EGL_NO_CONTEXT) {
            val eglSurfaceAttrs = intArrayOf(EGL14.EGL_WIDTH, mWidth, EGL14.EGL_HEIGHT, mHeight, EGL14.EGL_NONE) //以传入的宽高作为eglSurface
            mEGLSurface = EGL14.eglCreatePbufferSurface(mEGLDisplay, mEGLConfig, eglSurfaceAttrs, 0)
        }
        // 5.绑定EGLSurface和EGLContext到显示设备(EGLDisplay),但这个EGLDisplay的内容没有和View绑定,所以并不会直接显示
        if (mEGLSurface != null && mEGLSurface != EGL14.EGL_NO_SURFACE) {
            EGL14.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)
            mEglStatus = EglStatus.INITIALIZED
        }
        mIsCreated = true
        return true
    }

    // 销毁EGL环境
    fun destroyEGLEnv() {
        if (!mIsCreated) {
            return
        }
        // 与显示设备解绑
        EGL14.eglMakeCurrent(mEGLDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT)
        // 销毁 EGLSurface
        EGL14.eglDestroySurface(mEGLDisplay, mEGLSurface)
        // 销毁EGLContext
        EGL14.eglDestroyContext(mEGLDisplay, mEGLContext)
        // 销毁EGLDisplay(显示设备)
        EGL14.eglTerminate(mEGLDisplay)
        mEGLContext = null
        mEGLSurface = null
        mEGLDisplay = null
        mIsCreated = false
    }

    override fun finalize() {
        super.finalize()
        destroyEGLEnv()
    }

    // 请求渲染
    fun requestRender() {
        if (!mEglStatus.isValid) {
            return
        }
        if (mEglStatus == EglStatus.INITIALIZED) {
            mRenderer?.onSurfaceCreated()
            mEglStatus = EglStatus.CREATED
        }
        if (mEglStatus == EglStatus.CREATED) {
            mRenderer?.onSurfaceChanged(mWidth, mHeight)
            mEglStatus = EglStatus.CHANGED
        }
        if (mEglStatus == EglStatus.CHANGED || mEglStatus == EglStatus.DRAW) {
            mRenderer?.onDrawFrame()
            mEglStatus = EglStatus.DRAW
        }
    }

    fun getWidth() : Int {
        return mWidth
    }

    fun getHeight() : Int {
        return mHeight
    }
    
}

        类中核心逻辑在createEGLEnv方法中,负责根据制定的宽高、配置创建EGL环境。

        在当前线程把EGL环境创建后之后,就可以在当前线程中调用OpenGLES API进行渲染了。为了方便灵活地自定义GL接口的调用逻辑,和EGL环境的创建逻辑进行解耦,在这里写了一个回调接口IRenderer:

    // 渲染器接口
    interface IRenderer {
        fun onSurfaceCreated()
        fun onSurfaceChanged(width: Int, height: Int)
        fun onDrawFrame()
    }

        类中的requestRender方法其实就是在判断EGL环境创建就绪后,回调里面的自定义的onDrawFrame实现调用用户自定义的GL绘制逻辑。但实际上,在本线程中,只要你确认EGL已经创建成功,你可以在任意地方调用GL API,只是这样写可以用EGLMaker顺便帮自定义逻辑做一次EGL环境是否就绪的判断。

最小绘制测试逻辑:

        这里仅仅使用GLES30的API,做一次红色的清屏操作,然后使用glReadPixels获取里面的像素,然后显示到ImageView中,然后显示的是红色画面即代表EGL创建成功,而且OpenGL API能被成功调用。

测试逻辑搭建:

        测试渲染逻辑:

        由于只是渲染一个红色画面所以需要调用glClearColor当前渲染上下文状态颜色红色调用glClear并使用GL_COLOR_BUFFER_BIT参数FrameBuffer颜色组件进行即可,足够简单,甚至不需要启用GL_DEPTH_BUFFER_BIT

    override fun onDrawFrame() {
        GLES30.glClearColor(1f, 0f, 0f, 1f) //设定清理颜色为红色
        // 将颜色缓存区设置为预设的颜色
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT)
        //试试读出里面的像素,如果有颜色就代表这一阶段成功了
        if (mWidth != null && mHeight != null) {
            val byteBuffer = ByteBuffer.allocate(mWidth!! * mHeight!! * 4)
            GLES30.glReadPixels(0, 0, mWidth!!, mHeight!!, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, byteBuffer)
            if (mContentBmp == null || mContentBmp?.width != mWidth!! || mContentBmp?.height != mWidth!!) {
                mContentBmp = Bitmap.createBitmap(mWidth!!, mHeight!!, Bitmap.Config.ARGB_8888)
            }
            mContentBmp?.copyPixelsFromBuffer(byteBuffer)
        }
    }

        测试Activity

package com.cjztest.glOffscreenProcess.demo0

import android.app.Activity
import android.graphics.Color
import android.os.Bundle
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout

class OffScreenGLDemoActivity : Activity() {

    private lateinit var mEGL: EGLMaker

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val imageView = ImageView(this)

        val renderer = Renderer()
        mEGL = EGLMaker(this, 300, 300)
        mEGL.setRenderer(renderer)
        mEGL.requestRender()

        imageView.setImageBitmap(renderer.getBitmap())

        setContentView(imageView)
    }
}

        最终结果:

                原本没有颜色ImageView正确显示红色

结尾:

        GL上下文生效后,自然不止绘制这么简单画面,这个只是最简单的实例。实际可以按照以往正常OpenGL代码方式写好顶点shader片元shader编译后运行对应的shader program,以及执行纹理贴图逻辑顶点读取塞入缓冲逻辑实现复杂画面操作本文只是为了简化阅读难度,故意不引入更多复杂的操作,所以仅仅只是一次红色而已

  • 11
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是使用kotlin编写的安卓代码,用于从相册选择图片并将其显示ImageView。 首先,需要在AndroidManifest.xml文件添加以下权限: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ``` 然后,在Activity的onCreate方法添加以下代码: ```kotlin val intent = Intent(Intent.ACTION_PICK) intent.type = "image/*" startActivityForResult(intent, 1) ``` 这将打开系统的相册应用程序,让用户选择一个图片。 在Activity添加以下方法,以处理用户选择的图片: ```kotlin override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == 1 && resultCode == RESULT_OK && data != null) { val imageUri = data.data if (imageUri != null) { val bitmap = getBitmapFromUri(imageUri) imageView.setImageBitmap(bitmap) } } } private fun getBitmapFromUri(uri: Uri): Bitmap? { val inputStream = contentResolver.openInputStream(uri) return BitmapFactory.decodeStream(inputStream) } ``` 此代码首先检查requestCode和resultCode是否与我们启动Activity时使用的值匹配。如果匹配,则获取用户选择的图片的URI。 然后,我们调用getBitmapFromUri方法,该方法从URI获取输入流,并使用BitmapFactory将其解码为Bitmap对象。最后,我们将该位图设置到ImageView。 请注意,这种方法可能无法在Android 10正常工作,因为Android 10引入了一些更严格的文件访问权限。要在Android 10使用此方法,请确保在AndroidManifest.xml文件添加以下权限: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` 并使用以下代码打开图库: ```kotlin val intent = Intent(Intent.ACTION_OPEN_DOCUMENT) intent.type = "image/*" startActivityForResult(intent, 1) ``` 这将打开系统的文档应用程序,让用户选择一个图片。在处理用户选择的图片时,您需要使用以下方法获取输入流: ```kotlin private fun getBitmapFromUri(uri: Uri): Bitmap? { val inputStream = contentResolver.openInputStream(uri) return BitmapFactory.decodeStream(inputStream) } ``` 请注意,Android 10,您需要在AndroidManifest.xml文件添加以下权限: ```xml <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ``` 并在应用程序运行时请求这些权限。 以上是使用kotlin编写的安卓代码,用于从相册选择图片并将其显示ImageView,并保证在Android 10可用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值