Jump to content

Recommended Posts

Posted

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

Posted
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
Posted

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();
					}

 

Posted

But while I've got you here, @V0idWa1k3r, any ideas how I could get this working on blocks like signs and beds? The sprite being retrieved for those blocks is the purple/black missing texture pattern. Clearly retrieving their top quad isn't the way to go.

Posted

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.

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



  • Recently Browsing

    • No registered users viewing this page.
  • Posts

    • Im trying to build my mod using shade since i use the luaj library however i keep getting this error Reason: Task ':reobfJar' uses this output of task ':shadowJar' without declaring an explicit or implicit dependency. This can lead to incorrect results being produced, depending on what order the tasks are executed. So i try adding reobfJar.dependsOn shadowJar  Could not get unknown property 'reobfJar' for object of type org.gradle.api.internal.artifacts.dsl.dependencies.DefaultDependencyHandler. my gradle file plugins { id 'eclipse' id 'idea' id 'maven-publish' id 'net.minecraftforge.gradle' version '[6.0,6.2)' id 'com.github.johnrengelman.shadow' version '7.1.2' id 'org.spongepowered.mixin' version '0.7.+' } apply plugin: 'net.minecraftforge.gradle' apply plugin: 'org.spongepowered.mixin' apply plugin: 'com.github.johnrengelman.shadow' version = mod_version group = mod_group_id base { archivesName = mod_id } // Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. java.toolchain.languageVersion = JavaLanguageVersion.of(17) //jarJar.enable() println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" minecraft { mappings channel: mapping_channel, version: mapping_version copyIdeResources = true runs { configureEach { workingDirectory project.file('run') property 'forge.logging.markers', 'REGISTRIES' property 'forge.logging.console.level', 'debug' arg "-mixin.config=derp.mixin.json" mods { "${mod_id}" { source sourceSets.main } } } client { // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. property 'forge.enabledGameTestNamespaces', mod_id } server { property 'forge.enabledGameTestNamespaces', mod_id args '--nogui' } gameTestServer { property 'forge.enabledGameTestNamespaces', mod_id } data { workingDirectory project.file('run-data') args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') } } } sourceSets.main.resources { srcDir 'src/generated/resources' } repositories { flatDir { dirs './libs' } maven { url = "https://jitpack.io" } } configurations { shade implementation.extendsFrom shade } dependencies { minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" implementation 'org.luaj:luaj-jse-3.0.2' implementation fg.deobf("com.github.Virtuoel:Pehkui:${pehkui_version}") annotationProcessor 'org.spongepowered:mixin:0.8.5:processor' minecraftLibrary 'luaj:luaj-jse:3.0.2' shade 'luaj:luaj-jse:3.0.2' } // Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { manifest { attributes([ 'Specification-Title' : mod_id, 'Specification-Vendor' : mod_authors, 'Specification-Version' : '1', // We are version 1 of ourselves 'Implementation-Title' : project.name, 'Implementation-Version' : project.jar.archiveVersion, 'Implementation-Vendor' : mod_authors, 'Implementation-Timestamp': new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), "TweakClass" : "org.spongepowered.asm.launch.MixinTweaker", "TweakOrder" : 0, "MixinConfigs" : "derp.mixin.json" ]) } rename 'mixin.refmap.json', 'derp.mixin-refmap.json' } shadowJar { archiveClassifier = '' configurations = [project.configurations.shade] finalizedBy 'reobfShadowJar' } assemble.dependsOn shadowJar reobf { re shadowJar {} } publishing { publications { mavenJava(MavenPublication) { artifact jar } } repositories { maven { url "file://${project.projectDir}/mcmodsrepo" } } }  
    • All versions of Minecraft Forge suddenly black screen even without mods (tried reinstalling original Minecraft, Java, updating drivers doesn't work)
    • When i join minecraft all ok, when i join world all working fine, but when i open indentity menu, i get this The game crashed whilst unexpected error Error: java.lang.NullPointerException: Cannot invoke "top.ribs.scguns.common.Gun$Projectile.getDamage()" because "this.projectile" is null crash report here https://paste.ee/p/0vKaf
  • Topics

×
×
  • Create New...

Important Information

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