Hello everyone,
I've been getting some good support in the Discord about this recently, but still haven't been able to resolve my problems. I figured I'd create a post here now that the forums are back online.
What I'm trying to do: when a player types a command, I want to export an average pixel color value for the top surface of all blocks. What I've managed to do so far is to obtain the TextureAtlasSprite that I believe corresponds to the top surface of every block in the game. I cannot for the life of me figure out how to read the actual pixel values of this sprite though.
Here's what I have so far:
// Flag for whether the player has executed a color map generation request.
public static boolean MAPPING = false;
@SubscribeEvent
public static void onTickingPlayer(PlayerTickEvent event) {
if (MAPPING) {
MAPPING = false;
EntityPlayer sender = event.player;
sender.sendMessage(new TextComponentString(TextFormatting.GREEN + "Exporting all block textures..."));
// Find the texture of each block.
for (Block block : ForgeRegistries.BLOCKS) {
for (IBlockState blockState : block.getBlockState().getValidStates()) {
// Filter to test on just cobble for now.
if (blockState.toString().equals("minecraft:cobblestone")) {
// Retrieve the top quad of the block.
System.out.println(blockState.toString());
IBakedModel bakedModel = Minecraft.getMinecraft().getBlockRendererDispatcher()
.getBlockModelShapes().getModelForState(blockState);
List<BakedQuad> quadList = bakedModel.getQuads(blockState, EnumFacing.UP, 0L);
TextureAtlasSprite sprite = quadList.isEmpty() ? bakedModel.getParticleTexture()
: quadList.get(0).getSprite();
if (sprite == null) {
System.out.println("null");
// If the sprite exists, parse its pixel information.
} else {
int width = sprite.getIconWidth();
int height = sprite.getIconHeight();
int bytesPerPixel = 4;
ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
Minecraft.getMinecraft().getTextureManager()
.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
GL11.glReadPixels(sprite.getOriginX(), sprite.getOriginY(), width, height, GL11.GL_RGBA,
GL11.GL_UNSIGNED_BYTE, pixels);
// Debug print an image of the single block.
int r = 0, g = 0, b = 0;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
int i = (x + (width * y)) * bytesPerPixel;
r = pixels.get(i) & 0xFF;
g = pixels.get(i + 1) & 0xFF;
b = pixels.get(i + 2) & 0xFF;
image.setRGB(x, height - (y + 1), (0xFF << 24) | (r << 16) | (g << 8) | b);
}
}
File file = new File("test_cobblestone.png");
String format = "png";
try {
ImageIO.write(image, format, file);
} catch (IOException e) {
e.printStackTrace();
}
// Debug print the atlas.
int atlasSize = 112;
ByteBuffer atlasPixels = BufferUtils.createByteBuffer(atlasSize * atlasSize * 4);
Minecraft.getMinecraft().getTextureManager()
.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
GL11.glReadPixels(0, 0, atlasSize, atlasSize, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE,
atlasPixels);
BufferedImage atlasImage = new BufferedImage(atlasSize, atlasSize,
BufferedImage.TYPE_INT_RGB);
for (int x = 0; x < atlasSize; x++) {
for (int y = 0; y < atlasSize; y++) {
int i = (x + (atlasSize * y)) * bytesPerPixel;
int atlasR = atlasPixels.get(i) & 0xFF;
int atlasG = atlasPixels.get(i + 1) & 0xFF;
int atlasB = atlasPixels.get(i + 2) & 0xFF;
atlasImage.setRGB(x, atlasSize - (y + 1),
(0xFF << 24) | (atlasR << 16) | (atlasG << 8) | atlasB);
}
}
File atlasFile = new File("test_atlas.png");
String atlasFormat = "png";
try {
ImageIO.write(atlasImage, atlasFormat, atlasFile);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
}
What I was trying to do with this code above is print out as debug two images, "test_cobblestone.png" and "test_atlas.png" which I would expect to correspond to the 16x16 area of the texture atlas for the cobblestone surface and to a larger 112x112 snapshot of the texture atlas, respectively. What I find instead is that they correspond to effectively partial screenshots of my current view when running the command! I've attached them to this post for reference. I put this code into a tick handler just in case it was somehow being broken when attempting to execute immediately off of the command.
What it seems to me is going on is an error somewhere in this part of my code:
int width = sprite.getIconWidth();
int height = sprite.getIconHeight();
int bytesPerPixel = 4;
ByteBuffer pixels = BufferUtils.createByteBuffer(width * height * bytesPerPixel);
Minecraft.getMinecraft().getTextureManager()
.bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
GL11.glReadPixels(sprite.getOriginX(), sprite.getOriginY(), width, height, GL11.GL_RGBA,
GL11.GL_UNSIGNED_BYTE, pixels);
It looks like "GL11.glReadPixels" is reading my screen and not the texture atlas.
Any and all help here would be appreciated. I know that I can maybe achieve the same goal by reading the textures from disk, but really want to better understand how I can actually read textures from the block texture atlas.
Thank you!