我有点理解它在做什么,但下面提供的代码中的步骤背后的逻辑是什么?这是一种在LWJGL中加载纹理的方法。但是for循环中发生了什么?难道你不只是乘以x和y来获得像素的位置吗?从for循环到代码结尾的任何解释都会有所帮助,因为当它到达for循环时,注释变得模糊不清。将像素信息放入缓冲区时,我不明白奇怪的符号。
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;
}
}
答案 0 :(得分:2)
它所做的只是逐个像素地将图像中的颜色加载到缓冲区中。
此代码使用Java中的按位运算符执行此操作。请参阅此Java trail。
当您看到>>
时,这意味着“将此数字的二进制文件移到右侧”,当您看到num >> n
时,这意味着“移动num
的二进制文件值n
位于右侧。例如:
System.out.println(4 >> 2); // Prints "1"
这会打印1
,因为二进制4是0100
,当右移2位时,得到0001
,即十进制为1。
现在,正如所说,图像中的颜色使用ARGB表示。这意味着图像中的每32位都有8位专用于A,R,G和B(alpha,red,green和blue),因此其十六进制形式如下所示:
0xAARRGGBB
每个字母都是十六进制数字。
您发布的代码是使用二进制逻辑来检索每组AA
,RR
等。每个组正好是一个字节,或8位,所以这就是8,16 ,24来自。 &
对两个数字执行按位逻辑AND
,其中只有两个数字中1的位位置保持为1,而其他每个位置都变为0。
对于一个具体的例子,让我们从ARGB中的紫色中检索RR
字节。
在ARGB中,黄色为A=255
,R=127
,G=0
和B=127
,因此我们的十六进制版本为:
0xAARRGGBB
0xFF7F007F
查看十六进制值,我们看到RR
是结尾的第三个字节。要在ARGB值在变量中时获得RR
,让我们首先将其放入int pixel
:
int pixel = 0xFF7F007F;
请注意与代码的相似之处。像素矩阵中的每个int
都是ARGB颜色。
接下来,我们将数字右移2个字节,因此RR
是最低字节,这给了我们:
0x0000AARR
0x0000FF7F
这是通过以下代码完成的:
int intermediate = pixel >> 16;
16
来自于我们想要向右移2个字节,每个字节包含8位的事实。 >>
运算符需要位,所以我们必须给它16而不是2。
接下来,我们想摆脱AA
,但保留RR
。为此,我们使用所谓的位掩码和&
运算符。位掩码用于挑出二进制数的特定位。在这里,我们想要0xFF
。这正是二进制中的八个1。 (十六进制中的每个F
都是二进制的1111
。)
忍受我,因为这看起来很难看。当我们做int red = intermediate & 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)
请记住,&
表示如果两个输入位均为1,则结果位仅为1.
所以我们得到了red = 0x7F
或red = 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
会导致迭代转为0
,3
,6
,{ {1}},1
,4
,7
,2
,5
,这是不受欢迎的,因为我们需要将颜色添加到字节缓冲区中与像素阵列的顺序相同。
答案 1 :(得分:0)
每个像素由32位整数表示。该整数的最左边八位是其alpha分量,后跟红色,接着是绿色,接着是蓝色。
(pixel >> 16) & 0xFF
将整数16位向右移位,因此其中最右边的8位现在是红色分量。然后它使用位掩码将所有其他位设置为零,因此您只剩下红色组件。同样的逻辑适用于其他组件。
答案 2 :(得分:0)
奇怪的符号是移位运算符,我认为是按位AND运算符。
>> n
向右移位n位
&& 0xFF
表示您获取给定二进制值的最低8位
简而言之:for循环将pixel
变量分解为4个不同的8位部分:最高的8位是 alpha ,第二个是 red ,第三个是绿色,最后一个是蓝色
所以这是32位的地图:
AAAAAAAARRRRRRRRGGGGGGGGBBBBBBBB
其中:
答案 3 :(得分:0)
RGBA的每个分量(红色,绿色,蓝色,alpha)都有256 = 2 ^ 8(= 1个字节)的不同值。连接每个组件会产生一个32位二进制字符串,for循环按字节顺序加载到缓冲区中。