相机之使用OpenGL拍照

相机之使用OpenGL预览

使用OpenGL拍照

因为使用OpenGL显示摄像头数据的最后一步是,使用 ScreenFilter 将 FBO 中的数据显示到界面上,因此,可以在 ScreenFilter 做拍照处理,因为这时的数据,已经经过了各种处理,保存这时候的数据,就是显示到屏幕上的画面,而不是摄像头原始数据

修改ScreenFilter 如下:

class ScreenFilter(context: Context, val width: Int, val height: Int) :
    BaseFilter(context, R.raw.screen_vertex, R.raw.screen_frag) {
    companion object {
        private const val TAG = "ScreenFilter"
    }

    //照片回调
    private lateinit var pictureCallBack: (Bitmap) -> Unit

    //是否需要保存当前帧
    private var needSaveFrame: Boolean = false

    /**
     * 画到屏幕上
     */
    override fun onDrawFrame(textureId: Int): Int {
        GLES20.glUseProgram(mProgram)
        //绑定顶点和纹理数据
        bindData()

        //激活纹理单元0
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0)
        //将textureId纹理绑定到纹理单元0
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId)
        //将被激活的纹理单元0传给片段着色器中的vTexture,告诉vTexture采样器从纹理单元0读取数据
        GLES20.glUniform1i(vTexture, 0)
        //在textureId纹理上画出图像
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4)

        /**
         * 是否保存这一帧
         */
        if (needSaveFrame) {
            saveFrame()
            needSaveFrame = false
        }
        //解除绑定
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)
        return textureId
    }

    /**
     * 保存这一帧textureId纹理的数据
     */
    private fun saveFrame() {
        //分配一个ByteBuffer用于存储图像数据
        var buffer = ByteBuffer.allocate(width * height * 4).order(ByteOrder.BIG_ENDIAN)
        var startTime = System.currentTimeMillis()
        Log.d(TAG, "saveFrame: $startTime")
        // GLES20.GL_RGBA表示将数据以RGBA格式读取到buffer中,并且是以从左下角读到右上角的方式读
        // 如果使用小端Buffer,接收的就是ABGR Byte组数,那就要使用如下方法转换为ARGB
        // colors[i] = c and -0x00ff0100 or (c and 0x00ff0000 shr 16) or (c and 0x000000ff shl 16)
        GLES20.glReadPixels(0, 0, width, height, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buffer)
        buffer.rewind()
        var endTime = System.currentTimeMillis()
        Log.d(TAG, "saveFrame: ${endTime - startTime}")

        //在线程中进行上下翻转
        thread {
            val pixelCount = width * height
            val colors = IntArray(pixelCount)
            buffer.asIntBuffer().get(colors)
            //上下翻转
            for (y in 0 until height / 2) {
                for (x in 0 until width) {
                    var temp: Int = colors[(height - y - 1) * width + x]
                    // glReadPixels方法是从左下角读到右上角,需要进行上下翻转
                    colors[(height - y - 1) * width + x] = colors[y * width + x]
                    colors[y * width + x] = temp
                }
            }
            // 现在每个int类型的像素是接收到的RGBA,但bitmap需要ARGB格式,所以需要进行转换,否则颜色异常
            for (i in 0 until pixelCount) {
                val c = colors[i]
                colors[i] = (c shl 24) or (c shr 8)
            }
            //用图像数据创建一个bitmap
            var image = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888)
            //将bitmap回调回pictureCallBack
            pictureCallBack(image)
        }
    }

    /**
     * 设置needSaveFrame为true,表示要保存当前一帧,通过pictureCallBack回调进行保存
     */
    fun setSaveFrame(pictureCallBack: (Bitmap) -> Unit) {
        this.pictureCallBack = pictureCallBack
        this.needSaveFrame = true
    }
}

加了一个标志位 needSaveFrame,用于表示是否要保存当前帧,如果需要,就将当前帧的数据转换为一个Bitmap,需要注意的是,glReadPixels读取数据是以RGBA格式读取到buffer中,并且是以从左下角读到右上角的方式读,因此需要进行上下翻转和格式转换,最后用 pictureCallBack 将 Bitmap 回调回去,就可以进行图片保存了,如下:

//点击开始拍照
binding.takePicture.setOnClickListener {
    renderer.takePicture { bitmap ->
        //对拍照回调的bitmap进行保存
        var picturePath = FileUtil.getFilePath(FileUtil.PICTURE_SUFFIX)
        Log.d(TAG, "onCreateView: $picturePath " + Thread.currentThread().name)
        FileOutputStream(picturePath).use {
            bitmap.compress(Bitmap.CompressFormat.JPEG, 100, FileOutputStream(picturePath))
        }
        bitmap.recycle()
    }
}

你可能感兴趣的:(相机之使用OpenGL拍照)