Jump to content

[1.7.10] Help with getting raw texture data


SuperKael

Recommended Posts

I am trying to determine the "average color" of a texture, for various purposes, and I have everything figured out except, I need some way to get the texture of an item the form of an Image, Int[], or Int[][]. I am completely stumped, it seems like the Minecraft source code somehow never manages to actually reference images anywhere in the code. it just uses IIcons and and TextureMaps, and I simply can't figure it out. In order to obtain said texture, I have access to IIcons, the string name, etc. Someone please tell me how I can accomplish this.

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

I was trying to do that (for blocks) months ago and never figured it out.  I specifically wanted to pre-multiply two icons so they could be rendered and have correct particles, but gave up and did it as two passes (and have transparent particles).

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

Sadly, that won't work for me. I am trying to make "universal tools" that can be made from anything. say, an emerald pickaxe. it needs to dynamicaly determine that emeralds are green, and such make the head of the pickaxe look green. I have everything set up, I just need the raw texture :/

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

Sorry for the late reply. I am aware of this, my pickaxe actually uses 5 passes for each part of it to be rendered individually.

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

The raw textures are inserted into a single texture sheet during initialisation, and after that the Icon just tells the coordinates of the rectangular part of the large texture sheet that corresponds to that item.  For example the pickaxe might occupy the rectangle from [u=0.3, v = 0.1] to [u=0.4, v=0.2]

 

So I think there are two ways you can go - either extract the texture name from the Item's model and reload it from the file, or use OpenGL to render the relevant part of the texture sheet into a FrameBufferObject and read it back out.

 

I've never done it myself but a quick google shows up keywords like PixelBufferObject, glReadPixels that seem suitable.  It will be tricky unless you know a bit about OpenGL, I think.

 

-TGG

Link to comment
Share on other sites

The raw textures are inserted into a single texture sheet during initialisation, and after that the Icon just tells the coordinates of the rectangular part of the large texture sheet that corresponds to that item.

 

Yes, but that large texture sheet isn't referenced by anything anywhere.  Well, it probably is, but it's buried so deep that I have never been able to find it.

 

As for dealing with OGL, once you have raw pixel data, it's not too bad.  I did this by operating on the raw bitmap data (arrays of integers).  I actually utilized some code from that project for a block I'm working on, as I wanted to simulate rock fracture planes better than destroyed/not destroyed from explosions.  So I snagged the 2D line-drawing code from that project, converted it to handle 3D, and did the research for the 3D rotation of an arbitrary vector around another arbitrary vector (so I could draw 3D lines that went from the destroyed block, away from the explosion, but at a random angular offset).

 

Anyway, towards this problem:

either extract the texture name from the Item's model and reload it from the file, or use OpenGL to render the relevant part of the texture sheet into a FrameBufferObject and read it back out

 

Neither will do much good if we can't save the data back into the texture sheet.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

I think the FrameBufferObject may be the way to go. Because I don't need to write back onto the texture sheet. after looking at the code for spawn eggs, I can give a color tint to the texture on a pass-by-pass basis, so I just make grayscale template images, and color them like so.

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

The raw textures are inserted into a single texture sheet during initialisation, and after that the Icon just tells the coordinates of the rectangular part of the large texture sheet that corresponds to that item.

 

Yes, but that large texture sheet isn't referenced by anything anywhere.  Well, it probably is, but it's buried so deep that I have never been able to find it.

The texture is stored in OpenGL, not as an integer array any more.  The texture index is stored in TextureMap.mapTextureObjects and is accessed using

        this.mc.getTextureManager().bindTexture(TextureMap.locationBlocksTexture);

See for example

SimpleTexture.loadTexture() and TextureMap.loadTextureAtlas() (for the stitched-together block textures)

 

either extract the texture name from the Item's model and reload it from the file, or use OpenGL to render the relevant part of the texture sheet into a FrameBufferObject and read it back out

 

Neither will do much good if we can't save the data back into the texture sheet.

This is very easy to do with  DynamicTexture, for example this snippet which stores custom texture info for all six faces for a list of custom blocks

 

public class SelectionBlockTextures {


  public SelectionBlockTextures(TextureManager i_textureManager)
  {
    textureManager = i_textureManager;
    final int BLOCK_COUNT = 1;
    final int NUMBER_OF_FACES_PER_BLOCK = 6;
    final int U_TEXELS_PER_FACE = 16;
    final int V_TEXELS_PER_FACE = 16;
    int textureWidthTexels = BLOCK_COUNT * U_TEXELS_PER_FACE;
    int textureHeightTexels = NUMBER_OF_FACES_PER_BLOCK * V_TEXELS_PER_FACE;
    TEXELHEIGHTPERFACE = 1.0 / NUMBER_OF_FACES_PER_BLOCK;
    TEXELWIDTHPERBLOCK = 1.0 / BLOCK_COUNT;

    blockTextures = new DynamicTexture(textureWidthTexels, textureHeightTexels);
    textureResourceLocation = textureManager.getDynamicTextureLocation("SelectionBlockTextures", blockTextures);

    // just for now, initialise texture to all white
    // todo make texturing properly
    int [] rawTexture = blockTextures.getTextureData();

    for (int i = 0; i < rawTexture.length; ++i) {
      rawTexture[i] = Color.WHITE.getRGB();
    }

    blockTextures.updateDynamicTexture();
  }

  public void bindTexture() {
    textureManager.bindTexture(textureResourceLocation);
  }

  public SBTIcon getSBTIcon(IBlockState iBlockState, EnumFacing whichFace)
  {
    double umin = 0.0;
    double vmin = 0.0;
    return new SBTIcon(umin, umin + TEXELWIDTHPERBLOCK, vmin, vmin + TEXELHEIGHTPERFACE);
  }

  private final DynamicTexture blockTextures;
  private final ResourceLocation textureResourceLocation;
  private final TextureManager textureManager;
  private final double TEXELWIDTHPERBLOCK;
  private final double TEXELHEIGHTPERFACE;

  public class SBTIcon
  {
    public SBTIcon(double i_umin, double i_umax, double i_vmin, double i_vmax)
    {
      umin = i_umin;
      umax = i_umax;
      vmin = i_vmin;
      vmax = i_vmax;
    }

    public double getMinU() {
      return umin;
    }

    public double getMaxU() {
      return umax;
    }

    public double getMinV() {
      return vmin;
    }

    public double getMaxV() {
      return vmax;
    }

    private double umin;
    private double umax;
    private double vmin;
    private double vmax;
  }

}

 

-TGG

Link to comment
Share on other sites

  • 3 weeks later...

A Quick followup on how it went. I got it working! I didn't use FrameBufferObject or any of what you guys where suggesting, but you DID get me looking into LWJGL and OpenGL, and in the process I found my solution.

 

public int getAverageColor(ItemStack item){
	TextureAtlasSprite tas = (TextureAtlasSprite)item.getIconIndex();
	ByteBuffer pixels = BufferUtils.createByteBuffer(Math.max(tas.getIconWidth() * tas.getIconHeight(),1024));
	//if(Block.getBlockFromItem(item.getItem()) == Blocks.air){
		GL11.glReadPixels(tas.getOriginX(), tas.getOriginY(), tas.getIconWidth(), tas.getIconHeight(), GL11.GL_RGBA, GL11.GL_BYTE, pixels);
	//}
	byte[] texture = new byte[pixels.remaining()];
	pixels.get(texture);
	//BufferedImage bufimage = new BufferedImage(texture.length, texture.length, BufferedImage.TYPE_INT_ARGB);
	int r = 0,g = 0,b = 0;
	for (int i = 0; i < Math.sqrt(texture.length); i++) {
        for (int j = 0; j < Math.sqrt(texture.length); j++) {
            byte pixel = texture[(i + (j * tas.getIconWidth()))];
            Color color = new Color(pixel);
            r += color.getRed();
            g += color.getGreen();
            b += color.getBlue();
        }
    }
	r /= texture.length;
	g /= texture.length;
	b /= texture.length;
	return new Color(r,g,b).getRGB();
}

Thanks for all your help!

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

Nice!  Do you call this every time the item is rendered, or does it need to be done just once?

Actually your code would only need to run once, you're just trying to get an average color (that doesn't change).  But if I were to alter the pixel data, would it have to be done repeatedly?

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

Ok, turns out it doesn't QUITE work... for some reason it's referencing the lightmap instead of the texturemap, resulting in an item that is flashing various different colors... but yes, it would need to be re-called whenever the pixel data where to change.

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

Ok, turns out it doesn't QUITE work... for some reason it's referencing the lightmap instead of the texturemap, resulting in an item that is flashing various different colors... but yes, it would need to be re-called whenever the pixel data where to change.

 

I meant that if I wanted to use that code to change the pixel data, would it cache?

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

well no, because not only does the above code not work, but even so, it was set up to work entirely read-only. Although, I did finally make working code! (this time I did proper testing xD) sadly, this version actually accesses the texture files from the file system, which is not what I wanted... but it will do.

 

public int getAverageColor(ItemStack item){
	InputStream is;
	BufferedImage image;
	try {
		UniqueIdentifier UID = GameRegistry.findUniqueIdentifierFor(item.getItem());
		String itemID;
		if(Block.getBlockFromItem(item.getItem()).equals(Blocks.air)){
			itemID = UID.modId + ":textures/items/" + UID.name + ".png";
		}else{
			itemID = UID.modId + ":textures/blocks/" + UID.name + ".png";
		}
		is = Minecraft.getMinecraft().getResourceManager().getResource((new ResourceLocation(itemID))).getInputStream();
	    image = ImageIO.read(is);
	}catch(IOException e){e.printStackTrace();return 0;}
	int[] texture = new int[image.getWidth() * image.getHeight() * 4];
	texture = image.getRaster().getPixels(image.getRaster().getMinX(), image.getRaster().getMinY(), image.getRaster().getWidth(), image.getRaster().getHeight(), texture);
	int r = 0,g = 0,b = 0;
	int rloops = 0,gloops = 0,bloops = 0;
	for (int i = 0; i < texture.length; i++) {
		try{
			if(((float)i / 4) * 4 == i && texture[i + 3] >= 255){
				r += texture[i];
				rloops++;
			}
			if(((float)(i - 1) / 4) * 4 == i - 1 && texture[i + 2] >= 255){
				g += texture[i];
				gloops++;
			}
			if(((float)(i - 2) / 4) * 4 == i - 2 && texture[i + 1] >= 255){
				b += texture[i];
				bloops++;
			}
		}catch(Exception e){}
    }
	try{
		r /= rloops;
		g /= gloops;
		b /= bloops;
	}catch(Exception e){}
	return new Color(r,g,b).getRGB();
}

 

As for you plan, you could technically change the pixel data as it's looping, although you would need to add some way to do that dynamically. and yes, if you ever made such a change, you would have to make sure you re-call the method in order to get the result.

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

Kind of why I was hoping to be able to "precompute" the icons so the alpha multiplication step can be skipped.  Oh well.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

  • 6 months later...

(Was this really back from April?  Wow.  No wonder it took me a while to locate the thread.)

 

Woo, I got a block texture that's pre-computed!  It takes two icon registration strings and reads the files directly, doing alpha-multiplication.  Not bad for 1 day's work.

 

package com.draco18s.texturetest.client;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

import javax.imageio.ImageIO;

import com.draco18s.texturetest.TextureTestMain;
import com.google.common.collect.Lists;

import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.DynamicTexture;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.renderer.texture.TextureMap;
import net.minecraft.client.renderer.texture.TextureUtil;
import net.minecraft.client.resources.IResourceManager;
import net.minecraft.client.resources.data.AnimationMetadataSection;
import net.minecraft.util.IIcon;
import net.minecraft.util.ResourceLocation;

public class TextureAtlasDynamic extends TextureAtlasSprite {
protected String textureBase;
protected String textureOverlay;

public TextureAtlasDynamic(String p_i1282_1_, String base, String overlay) {
	super(p_i1282_1_);
	textureBase = base;
	textureOverlay = overlay;
}

@Override
public void updateAnimation() {
	TextureUtil.uploadTextureMipmap((int[][])this.framesTextureData.get(0), this.width, this.height, this.originX, this.originY, false, false);
}

@Override
public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) {
        return true;
    }

@Override
    public boolean load(IResourceManager manager, ResourceLocation location) {
	framesTextureData.clear();
        //this.setFramesTextureData(Lists.newArrayList());
        this.frameCounter = 0;
        this.tickCounter = 0;
        
        //int [] rawTexture = icon.getTextureData();
        
        //IResourceManager manager = Minecraft.getMinecraft().getResourceManager();
        try {
        	ResourceLocation resource1 = new ResourceLocation(textureBase);
        	ResourceLocation resource2 = new ResourceLocation(textureOverlay);
        	TextureMap map = Minecraft.getMinecraft().getTextureMapBlocks();
        	resource1 = this.completeResourceLocation(map, resource1, 0);
        	resource2 = this.completeResourceLocation(map, resource2, 0);
		BufferedImage buff = ImageIO.read(manager.getResource(resource1).getInputStream());
		BufferedImage buff2 = ImageIO.read(manager.getResource(resource2).getInputStream());

        width = buff.getWidth();
        height = buff.getHeight();

        int[] rawBase = new int[buff.getWidth()*buff.getHeight()];
        int[] rawOverlay = new int[buff2.getWidth()*buff2.getHeight()];

        buff.getRGB(0, 0, buff.getWidth(), buff.getHeight(), rawBase, 0, buff.getWidth());
        buff2.getRGB(0, 0, buff2.getWidth(), buff2.getHeight(), rawOverlay, 0, buff2.getWidth());
        
        int min = Math.min(buff.getWidth(),buff2.getWidth());
        rawBase = scaleToSmaller(rawBase, buff.getWidth(), min);
        rawOverlay = scaleToSmaller(rawOverlay, buff2.getWidth(), min);
        
        int r1,g1,b1;
        int r2,g2,b2;
        float a1,a2,a3;
        for(int p=0; p<rawBase.length;p++) {
        	//perform alpha blending
        	Color c1 = new Color(rawBase[p],true);
        	Color c2 = new Color(rawOverlay[p],true);
        	
        	a1 = c1.getAlpha()/255f;
        	r1 = c1.getRed();
        	g1 = c1.getGreen();
        	b1 = c1.getBlue();
        	a2 = c2.getAlpha()/255f;
        	r2 = c2.getRed();
        	g2 = c2.getGreen();
        	b2 = c2.getBlue();
        	a3 = a2+a1*(1-a2);
        	
        	if(a3 > 0) {
	        	r1 = Math.round(((r2*a2)+(r1*a1*(1-a2)))/a3);
	        	g1 = Math.round(((g2*a2)+(g1*a1*(1-a2)))/a3);
	        	b1 = Math.round(((b2*a2)+(b1*a1*(1-a2)))/a3);
        	}
        	else {
        		r1 = g1 = b1 = 0;
        	}
        	Color c3 = new Color(r1,g1,b1,Math.round(a3*255));
        	rawBase[p] = c3.getRGB();
        	//rawBase[p] = (rawBase[p] + rawOverlay[p])/2;
        }
        
        int[][] aint = new int[1 + MathHelper.calculateLogBaseTwo(min)][];
        for (int k = 0; k < aint.length; ++k)
        {
        	aint[k] = rawBase;
        }

        this.framesTextureData.add(aint);
	} catch (IOException e) {
		e.printStackTrace();
	}
        return false;
    }
/** Scale the larger icon to the same size as the smaller, using no interpolation.  This is "good enough" to accommodate most resource packs.
* Alternatively could scale up in the same manner.
* width and min should be multiples (dividing evenly) due to the power-of-2 rule.
*/
    private int[] scaleToSmaller(int[] data, int width, int min) {
	if(width == min) { return data; }
	int scale = width / min;
	int[] output = new int[min*min];
	int j = 0;
	for(int i = 0; i < output.length; i++) {
		if(i%min == 0) {
			j += width;
		}
		output[i] = data[i*scale+j];
	}
	return output;
}

/** Wrapper to access the TextureMap private method.
*/
private ResourceLocation completeResourceLocation(TextureMap map, ResourceLocation loc, int c) {
        try {
		return (ResourceLocation)TextureTestMain.proxy.resourceLocation.invoke(map, loc, c);
	} catch (IllegalAccessException e) {
		e.printStackTrace();
	} catch (IllegalArgumentException e) {
		e.printStackTrace();
	} catch (InvocationTargetException e) {
		e.printStackTrace();
	}
        return null;
    }
}

 

There's a bit of reflection necessary to turn "modid:texture" into a proper directory listing, which I do in the client proxy.  This finds and saves a reference to the private

completeResourceLocation

method of TextureMap.

	public void init() {
	Class clz = TextureMap.class;
	Method[] meths = clz.getDeclaredMethods();
	for(Method m : meths) {
		if(m.getReturnType() == ResourceLocation.class) {
			m.setAccessible(true);
			resourceLocation = m;
		}
	}
}

 

And block registration:

 

	@Override
@SideOnly(Side.CLIENT)
public void registerBlockIcons(IIconRegister iconRegister) {
	if(iconRegister instanceof TextureMap) {
		TextureMap map = (TextureMap)iconRegister;
		map.setTextureEntry("texturetest:test", new TextureAtlasDynamic("texturetest:test",Blocks.stone.getIcon(0, 0).getIconName(),"texturetest:stone_overlay_13"));
		blockIcon = map.getTextureExtry("texturetest:test");
	}
}

 

width=800 height=449http://s12.postimg.org/pks9kuty5/2015_10_13_22_58_48.png[/img]

 

No more transparent particles!

Left is the custom block, right is vanilla cobblestone.

Apparently I'm a complete and utter jerk and come to this forum just like to make fun of people, be confrontational, and make your personal life miserable.  If you think this is the case, JUST REPORT ME.  Otherwise you're just going to get reported when you reply to my posts and point it out, because odds are, I was trying to be nice.

 

Exception: If you do not understand Java, I WILL NOT HELP YOU and your thread will get locked.

 

DO NOT PM ME WITH PROBLEMS. No help will be given.

Link to comment
Share on other sites

  • 3 years later...

Haha, how fun. I just managed to stumble across my own thread in a google search. Congrats on figuring out what you were doing, but man, but own code now disgusts me. I was wrapping divisions in try/catch to deal with divide by zeros... blegh. I sure have come a long way in the past few years :D. Oh, and sorry for the bit of thread necromancy, this thread just brought back a lot of memories.

If I ever say something stupid, or simply incorrect, please excuse me. I don't know anything about 1.8 modding, and I don't know much about entities either, But I try to help when I can.

Link to comment
Share on other sites

  • Guest locked this topic
Guest
This topic is now closed to further replies.


×
×
  • Create New...

Important Information

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