Jump to content

[1.12.2] Retrieving the pixel data for a block's texture


Rockhopper

Recommended Posts

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!

test_atlas.png

test_cobblestone.png

Link to comment
Share on other sites

31 minutes ago, Rockhopper said:

It looks like "GL11.glReadPixels" is reading my screen and not the texture atlas.

That's quite literally what readPixels is supposed to do - it reads the pixel values from the currently bound framebuffer. For the future you can read documentation about opengl methods here.

For actually reading the pixel values from a texture you need to use getTexImage. Note that it will dump the entire texture onto the buffer though.

You could render the part of the texture you need onto a framebuffer and read from that using readPixels but I don't think it is going to be faster than reading the entire texture.

  • Like 1
Link to comment
Share on other sites

Thank you @V0idWa1k3r! That was just the tip I needed to get this working. I found another post of yours elsewhere on the forum and ended up combining it with the advice here to come up with

						// Temporarily replace the frame buffer to write sprite data.
						int width = sprite.getIconWidth();
						int height = sprite.getIconHeight();
						Framebuffer currentFrame = Minecraft.getMinecraft().getFramebuffer();
						Framebuffer spriteFrame = new Framebuffer(width, height, true);
						spriteFrame.bindFramebuffer(true);
						GlStateManager.clearColor(0, 0, 0, 1);
						GlStateManager.clear(GL11.GL_COLOR_BUFFER_BIT);

						// Draw the actual block texture.
						GlStateManager.pushMatrix();
						float xScale = ((1.0f * Minecraft.getMinecraft().displayWidth)
								/ (1.0f * Minecraft.getMinecraft().displayHeight));
						GlStateManager.scale(xScale, 1, 1);
						Minecraft.getMinecraft().getTextureManager().bindTexture(TextureMap.LOCATION_BLOCKS_TEXTURE);
						Minecraft.getMinecraft().ingameGUI.drawTexturedModalRect(0, 0, sprite, 256, 256);
						GlStateManager.popMatrix();

						// Allocate a buffer for GL to dump pixels into.
						IntBuffer pixels = BufferUtils.createIntBuffer(width * height);
						GlStateManager.bindTexture(spriteFrame.framebufferTexture);

						// Dump the pixels onto the IntBuffer. Note that the pixel format is BGRA and
						// the pixel type is 8 bits per color.
						GlStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL12.GL_BGRA,
								GL12.GL_UNSIGNED_INT_8_8_8_8_REV, pixels);

						// Allocate the array to hold pixel values.
						int[] pixelValues = new int[width * height];
						pixels.get(pixelValues);
						TextureUtil.processPixelValues(pixelValues, width, height);

which seems to be working great.

 

Just in case it helps anyone else down the road, here's what I use to retrieve the sprite.

					// If a block is liquid, get its texture from the fluid registry.
					if (block instanceof IFluidBlock || block instanceof BlockLiquid) {
						Fluid fluid = FluidRegistry.lookupFluidForBlock(block);
						ResourceLocation fluidTexture = fluid.getStill();
						sprite = Minecraft.getMinecraft().getTextureMapBlocks().getAtlasSprite(fluidTexture.toString());

					// Retrieve the top quad of the solid block.
					} else {
						IBakedModel bakedModel = Minecraft.getMinecraft().getBlockRendererDispatcher()
								.getBlockModelShapes().getModelForState(blockState);
						List<BakedQuad> quadList = bakedModel.getQuads(blockState, EnumFacing.UP, 0L);
						sprite = quadList.isEmpty() ? bakedModel.getParticleTexture() : quadList.get(0).getSprite();
					}

 

Link to comment
Share on other sites

Unfortunately those blocks use a TESR and you can't really do this with a TESR. A possible solution would be to render a TESR onto a separate framebuffer, but then you would need a solution for blocks that use a FastTESR. It is possible in theory I suppose but you would have to look into the way vanilla+forge render TESRs.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Announcements



×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.