【问题标题】:Can someone explain this code step by step?有人可以逐步解释此代码吗?
【发布时间】:2012-10-14 20:46:40
【问题描述】:

我有点理解它在做什么,但是下面提供的代码中的步骤背后的逻辑是什么?这是在 LWJGL 中加载纹理的一种方式。但是 for 循环中发生了什么?你不只是将 x 和 y 相乘来得到一个像素的位置吗?任何从 for 循环到代码末尾发生的事情的解释都会有所帮助,因为当它到达 for 循环时,cmets 会变得模糊。将像素信息放入缓冲区时,我不明白奇怪的符号。

public class TextureLoader { 
private static final int BYTES_PER_PIXEL = 4;//3 for RGB, 4 for RGBA 
   public static int loadTexture(BufferedImage image){ 

      int[] pixels = new int[image.getWidth() * image.getHeight()]; 
        image.getRGB(0, 0, image.getWidth(), image.getHeight(), pixels, 0, image.getWidth()); 

        ByteBuffer buffer = BufferUtils.createByteBuffer(image.getWidth() * image.getHeight() * BYTES_PER_PIXEL); //4 for RGBA, 3 for RGB 

        for(int y = 0; y < image.getHeight(); y++){ 
            for(int x = 0; x < image.getWidth(); x++){ 
                int pixel = pixels[y * image.getWidth() + x]; 
                buffer.put((byte) ((pixel >> 16) & 0xFF));     // Red component 
                buffer.put((byte) ((pixel >> 8) & 0xFF));      // Green component 
                buffer.put((byte) (pixel & 0xFF));               // Blue component 
                buffer.put((byte) ((pixel >> 24) & 0xFF));    // Alpha component. Only for RGBA 
            } 
        } 

        buffer.flip(); //FOR THE LOVE OF GOD DO NOT FORGET THIS 

        // You now have a ByteBuffer filled with the color data of each pixel. 
        // Now just create a texture ID and bind it. Then you can load it using  
        // whatever OpenGL method you want, for example: 

      int textureID = glGenTextures(); //Generate texture ID 
        glBindTexture(GL_TEXTURE_2D, textureID); //Bind texture ID 

        //Setup wrap mode 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL12.GL_CLAMP_TO_EDGE); 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL12.GL_CLAMP_TO_EDGE); 

        //Setup texture scaling filtering 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 

        //Send texel data to OpenGL 
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, image.getWidth(), image.getHeight(), 0, GL_RGBA, GL_UNSIGNED_BYTE, buffer); 

        //Return the texture ID so we can bind it later again 
      return textureID; 
   } 

   public static BufferedImage loadImage(String loc) 
   { 
        try { 
           return ImageIO.read(DefenseStep.class.getResource(loc)); 
        } catch (IOException e) { 
            //Error Handling Here 
        } 
       return null; 
   } 

}

【问题讨论】:

标签: java 2d textures symbols


【解决方案1】:

它所做的只是将图像中的颜色逐个像素地加载到缓冲区中。

此代码使用 Java 中的按位运算符执行此操作。看到这个Java trail

当你看到&gt;&gt;时,这意味着“将这个数字的二进制向右移动”,当你看到num &gt;&gt; n时,这意味着“将num的值n的二进制位移动到右边。例如:

System.out.println(4 >> 2); // Prints "1"

这将打印1,因为二进制中的 4 是 0100,并且当右移 2 位时,您会得到 0001,即十进制中的 1。

话虽如此,图像中的颜色使用 ARGB 表示。这意味着图像中的每 32 位都有 8 位专用于 A、R、G 和 B(alpha、红色、绿色和蓝色),因此其十六进制形式如下所示:

0xAARRGGBB

每个字母都是一个十六进制数字。

您发布的代码使用二进制逻辑来检索每组AARR 等。每组恰好是一个字节或 8 位,所以这就是 8、16 和 24 的来源从。 &amp; 对这两个数字执行按位逻辑 AND,其中只有两个数字中为 1 的位位置保持 1,每隔一个位置变为 0。

举个具体的例子,让我们从 ARGB 中的紫色中检索 RR 字节。

在 ARGB 中,黄色是 A=255R=127G=0B=127,所以我们的十六进制版本是:

0xAARRGGBB
0xFF7F007F

查看十六进制值,我们看到RR 是倒数第三个字节。要在 ARGB 值在变量中时获取 RR,让我们首先将其放入 int pixel

int pixel = 0xFF7F007F;

请注意与您的代码的相似之处。像素矩阵中的每个int 都是一种ARGB 颜色。

接下来,我们将数字右移 2 个字节,因此 RR 是最低字节,这给了我们:

0x0000AARR
0x0000FF7F

这是通过以下代码完成的:

int intermediate = pixel >> 16;

这里的16来自于我们要右移2个字节,每个字节包含8位。 &gt;&gt; 运算符需要位,因此我们必须给它 16 而不是 2。

接下来,我们要删除AA,但保留RR。为此,我们使用所谓的位掩码和&amp; 运算符。位掩码用于挑选出二进制数的特定位。在这里,我们想要0xFF。这恰好是二进制中的八个 1。 (每个十六进制的F 是二进制的1111。)

请耐心等待,因为这看起来很难看。当我们执行int red = intermediate &amp; 0xFF 时,它在做什么(二进制)是:

  0000 0000 0000 0000 1111 1111 0111 1111 (0x00007F7F)
& 0000 0000 0000 0000 0000 0000 1111 1111 (0x000000FF)
= 0000 0000 0000 0000 0000 0000 0111 1111 (0x0000007F)

请记住,&amp; 表示如果两个输入位均为 1,则结果位仅为 1。

所以我们得到了 red = 0x7Fred = 127,这正是我们上面的内容。

编辑:

为什么他从y开始循环遍历图像的像素,然后是x,而不是x,然后是y?而当他创建变量pixel 时,为什么要将y 乘以宽度并加上x?获取像素不应该只是x * y吗?

让我们使用一个简单的 3x3 图片来演示。在 3x3 图像中,您有 9 个像素,这意味着 pixels 数组有 9 个元素。这些元素是由getRGB 以相对于图像的逐行顺序创建的,因此像素/索引关系如下所示:

0 1 2
3 4 5
6 7 8

位置对应于用于获取该像素的索引。所以要获得图像的左上角像素(0, 0),我使用pixel[0]。为了获得中心像素(1, 1),我使用pixel[4]。为了得到中心像素下的像素,(1, 2),我使用pixel[7]

请注意,这会生成图像坐标到索引的 1:1 映射,如下所示:

Coord. -> Index
---------------
(0, 0) -> 0
(1, 0) -> 1
(2, 0) -> 2
(0, 1) -> 3
(1, 1) -> 4
(2, 1) -> 5
(0, 2) -> 6
(1, 2) -> 7
(2, 2) -> 8

坐标是(x, y) 对,因此我们需要找出一种数学方法将 x 和 y 对转换为索引。

我可以学习一些有趣的数学,但为了简单起见,我不会这样做。让我们从您的建议开始,使用x * y 获取索引。如果我们这样做,我们会得到:

Coord. -> Index
-------------------
(0, 0) -> 0 * 0 = 0
(1, 0) -> 1 * 0 = 0
(2, 0) -> 2 * 0 = 0
(0, 1) -> 0 * 1 = 0
(1, 1) -> 1 * 1 = 1
(2, 1) -> 2 * 1 = 2
(0, 2) -> 0 * 2 = 0
(1, 2) -> 1 * 2 = 2
(2, 2) -> 2 * 2 = 4

这不是我们上面的映射,所以使用x * y 是行不通的。由于我们无法更改getRGB 对像素的排序方式,我们需要它来匹配上面的映射。

让我们试试他的解决方案。他的方程是x = y * w,其中w是宽度,在本例中为3:

Coord. -> Index
-----------------------
(0, 0) -> 0 + 0 * 3 = 0
(1, 0) -> 1 + 0 * 3 = 1
(2, 0) -> 2 + 0 * 3 = 2
(0, 1) -> 0 + 1 * 3 = 3
(1, 1) -> 1 + 1 * 3 = 4
(2, 1) -> 2 + 1 * 3 = 5
(0, 2) -> 0 + 2 * 3 = 6
(1, 2) -> 1 + 2 * 3 = 7
(2, 2) -> 2 + 2 * 3 = 8

看看这些映射如何与上面的映射对齐?这就是我们想要的。基本上y * w 在这里所做的是跳过数组中的第一个y * w 像素,这与跳过y 行像素完全相同。然后通过迭代x,我们正在迭代当前行的每个像素。

如果上面的解释不清楚,我们会遍历y 然后 x,因为像素是逐行添加到水平数组中的 (x)顺序,所以内部循环应该遍历x 值,这样我们就不会跳来跳去。如果我们使用相同的y * w + x,那么迭代x 然后y 将导致迭代进行036147、@ 987654405@、58,这是不可取的,因为我们需要按照与像素数组相同的顺序将颜色添加到字节缓冲区。

【讨论】:

  • 我可能应该在接受这个作为答案之前问一下,但我还有另一个问题。为什么他从 y 开始循环遍历图像的像素,然后是 x,而不是 x 然后 y?而当他创建变量“像素”时,为什么要将 y 乘以宽度并加上 x?获取像素不应该只是 x * y 吗?
【解决方案2】:

每个像素由一个 32 位整数表示。该整数的最左边八位是它的 alpha 分量,然后是红色,然后是绿色,最后是蓝色。

(pixel &gt;&gt; 16) &amp; 0xFF 将整数右移 16 位,因此其中最右边的 8 位现在是红色分量。然后它使用位掩码将所有其他位设置为零,因此您只剩下红色组件。相同的逻辑适用于其他组件。

Further reading.

【讨论】:

    【解决方案3】:

    奇怪的符号你指的是移位运算符,我认为是按位与运算符。

    &gt;&gt; n 右移 n 位

    &amp;&amp; 0xFF 表示你取给定二进制值的最低 8 位

    简而言之:for循环将pixel变量分解为4个不同的8位部分:最高8位将是alpha,第二个是red,第三个是green,最后一个是blue

    所以这是 32 位的地图:

    AAAAAAARRRRRRGGGGGGGGBBBBBBBB

    地点:

    • 答:阿尔法
    • R:红色
    • G:绿色
    • B:蓝色

    【讨论】:

      【解决方案4】:

      RGBA 的每个分量(红色、绿色、蓝色、alpha)都有 256 = 2^8(= 1 个字节)不同的值。连接每个组件会产生一个 32 位二进制字符串,for 循环将其按字节加载到缓冲区中。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多