在Android中创建位图之前,从字节缓冲区翻转openGL纹理

4

我正在使用一个依赖于 Google Grafika 仓库的直播流 API。我使用 Grafika EGLSurfaceBase 的 saveFrame 方法,允许用户在流媒体过程中捕捉视频帧。

https://github.com/google/grafika/blob/master/src/com/android/grafika/gles/EglSurfaceBase.java

实际捕获工作正常,但显然在某些相机方向上图像会翻转。 我找到了很多与从OpenGL纹理中获取反转位图相关的问题,但大多数似乎是涉及绘制图像并依赖于以下两种方法之一: a)在OpenG中翻转纹理。但在我的情况下,我正在使用实时流API工作,因此翻转纹理以捕获图像可能实际上也会翻转视频流中的图像捕获。 或者 b)在生成基于资源的位图后翻转位图。在我的情况下,我没有资源,我正在从字节缓冲区创建位图,并且不想复制它来翻转它。 这是API具有的基本EGLSurfaceBase方法-我将把相机方向传递给它,但我的问题是:
        String filename = file.toString();

    int width = getWidth();
    int height = getHeight();
    ByteBuffer buf = ByteBuffer.allocateDirect(width * height * 4);
    buf.order(ByteOrder.LITTLE_ENDIAN);
    GLES20.glReadPixels(0, 0, width, height,
            GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, buf);
    GlUtil.checkGlError("glReadPixels");
    buf.rewind();

    BufferedOutputStream bos = null;
    try {
        bos = new BufferedOutputStream(new FileOutputStream(filename));
        Bitmap bmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        bmp.copyPixelsFromBuffer(buf);
        bmp.compress(Bitmap.CompressFormat.PNG, 90, bos);
        bmp.recycle();
    } finally {
        if (bos != null) bos.close();
    }
    Log.d(TAG, "Saved " + width + "x" + height + " frame as '" + filename + "'");
}

我希望找到一种在BMP.createbitmap之前(或同时)翻转图像的方法。例如,我能否使用矩阵通过glReadPixels翻转像素的读取?
另一个注意点/想法:也许在创建后翻转位图的成本很小,因为由于依赖用户交互,它不会经常发生足以引起内存错误?
2个回答

8
您可以在glReadPixels之后反转ByteBuffer。这非常快,因为它只是内存复制。我的测试显示,反转操作只需要不到10毫秒。
以下是一个可行的实现:
    private void reverseBuf(ByteBuffer buf, int width, int height)
{
    long ts = System.currentTimeMillis();
    int i = 0;
    byte[] tmp = new byte[width * 4];
    while (i++ < height / 2)
    {
        buf.get(tmp);
        System.arraycopy(buf.array(), buf.limit() - buf.position(), buf.array(), buf.position() - width * 4, width * 4);
        System.arraycopy(tmp, 0, buf.array(), buf.limit() - buf.position(), width * 4);
    }
    buf.rewind();
    Log.d(TAG, "reverseBuf took " + (System.currentTimeMillis() - ts) + "ms");
}

谢谢你,亲爱的蜻蜓!这段代码现在被用于 YoWindow 天气应用程序中,以导出屏幕进行分享。 - Pavel

3
使用read pixels时,图像似乎总是翻转的,因为opengl呈现缓冲区中的第一个像素位于左下角。有两种方法可以获得正确的顺序。
一种方法是将其上下倒置并绘制到缓冲区中,这可以在单独的缓冲区上完成,并且不会干扰您当前的绘图流程。如果您想在单独的线程上执行此操作或者调整图像大小,这可能是一个非常好的选择。所有这些都可以在单个绘制调用中完成。
另一种方法是手动翻转数据,这并不像看起来那么糟糕,因为你只需要翻转行(列会很糟糕)。无论如何,您实际上可以在同一缓冲区上执行此操作,无需复制缓冲区。只需按照以下顺序交换行:保存第一行,将其替换为最后一行,将最后一行替换为已保存的一行...接着处理第二行即可。

非常感谢您的帮助。我必须承认,在OpenGL文档和OpenGL ES的具体细节之间,我完全迷失了方向。如果您有关于选项1(倒置缓冲区绘制)的链接或几行代码可以提供给我参考,那将帮助我离开Google回到代码中。 - Laurent
顺便说一下,在你回答之前,我的解决方案是创建位图#2,并使用变换矩阵将第一位图复制到其中(在保存数据到磁盘之前)。这也没有对性能产生太大影响,但我更注重时间而不是内存分配。 - Laurent
实际上这很简单,你需要做的就是生成一个新的帧缓冲和渲染缓冲,并像主缓冲一样将它们附加。之后,在绑定帧缓冲后设置正确的视口,然后可以像在主缓冲中一样调用绘图代码。唯一的区别是你需要在正交或透视矩阵中反转顶部和底部。完成后,只需绑定以前的帧缓冲即可继续。要获取更多详细信息,请搜索有关在FBO上绘制的内容或提出新问题。 - Matic Oblak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接