Click here to Skip to main content
15,881,821 members
Articles / Mobile Apps / Android

Texture Atlas Maker

Rate me:
Please Sign up or sign in to vote.
4.94/5 (16 votes)
1 Apr 2012BSD6 min read 142K   7K   55  
A utility to create texture atlases for 2D OpenGL games
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

	public void CropTransparent() 
	{
		int srcW = image.getWidth(null);
		int srcH = image.getHeight(null);
		int[] srcPixels = new int[srcW*srcH];
	    image.getRGB(0, 0, srcW, srcH, srcPixels, 0, srcW);
	    
		int srcX, srcY;
		int alpha;
		int pixel;
		int xFirst = srcW+1;
		int xLast = -1;
		int yFirst = srcH+1;
		int yLast = -1;
		int xFirstNoThresh = srcW+1;
		int yFirstNoThresh = srcH+1;
		transparent = false;
		for (srcY = 0; srcY < srcH; ++srcY) 
		{
			for (srcX = 0; srcX < srcW; ++srcX) 
			{
				pixel = srcPixels[srcX + srcY * srcW];
				alpha = (srcPixels[srcX + srcY * srcW] >>24) & 0xff;
				if (alpha > 2)
				{
					if (xLast < srcX) xLast = srcX;  
					if (xFirst > srcX) xFirst = srcX;  
					if (yLast < srcY) yLast = srcY;  
					if (yFirst > srcY) yFirst = srcY;  
				}
				if (alpha > 0)
				{
					if (xFirstNoThresh > srcX) xFirstNoThresh = srcX;  
					if (yFirstNoThresh > srcY) yFirstNoThresh = srcY;  
				}
				if (alpha < 254)
				{
					transparent = true;
				}
			}
		}
		int xSize = xLast-xFirst+1;
		int ySize = yLast-yFirst+1;
		BufferedImage imageCut;
		if (xSize < 1 || ySize < 1)
		{
	        imageCut = new BufferedImage(1, 1, 
	        		BufferedImage.TYPE_INT_ARGB);
	        System.out.println("Warning: image with no opaque pixels!" + fileName);
	        xOffset = 0;
	        yOffset = 0;
		}
		else
		{
	        imageCut = new BufferedImage(xSize, ySize, 
	        		BufferedImage.TYPE_INT_ARGB);
	        Graphics2D g = imageCut.createGraphics();
	        g.drawImage(image, null, -xFirst, -yFirst);		
	        g.dispose();
	        xOffset = xFirstNoThresh;
	        yOffset = yFirstNoThresh;
		}
		if (this.fileName.equals("twoStory"))
		{
	        System.out.println("Warning: image with no opaque pixels!" + fileName);
		}
//        try 
//        {
//            // Save as PNG
//            file = new File(fileName+"x");
//            ImageIO.write(imageCut, "png", file);       
//        } 
//        catch (Exception e)
//        {
//            System.out.println("error: "+e.toString());
//        }
		image = imageCut;
	}

    Collections.sort(listImage, new CompareHeight());
    listTexturePiece = treeBinPack(listImage, listSize[ixSize]);
	if (listTexturePiece != null) break;
    Collections.sort(listImage, new CompareWidth());
    listTexturePiece = treeBinPack(listImage, listSize[ixSize]);
	if (listTexturePiece != null) break;
	
	
    private static void writeRLE(DataOutputStream dataOut, int[] data) throws IOException 
    {
    	int index = 0;
    	// count pixels that are the same
//    	int count = 0;
    	int startPacket;
    	int packetHeader;
    	int n;
    	byte[] dataByte = new byte[data.length*4];
    	do
    	{
    		startPacket = index;
	    	while (index+1 < data.length && data[index] == data[index+1] &&
	    			index - startPacket < 127)
	    	{
	    		index++;
	    	}
	    	if (index - startPacket < 2)
	    	{
	    		// the packet is raw, now we have to count how many more bytes until we reach
	    		// a sequence of 3
		    	while (index+2 < data.length && (data[index] != data[index+1] || 
		    			data[index] != data[index+2]) &&
		    			index - startPacket < 127)
		    	{
		    		index++;
		    	}
	    		if (index+2 >= data.length) 
	    		{
	    			index = data.length;
	    		}
	    		packetHeader = (index - startPacket-1);
//		        System.out.println("header: "+packetHeader + " index:"+index);
	    		dataOut.writeByte(packetHeader);
	    		for (n = startPacket; n < index; n++)
	    		{
	    			writeBGRA(dataOut, data[n]);
	    		}
	    	}
	    	else
	    	{
	    		if (index+1 >= data.length) 
	    		{
	    			index = data.length-1;
	    		}
	    		// output rle packet
	    		packetHeader = 0x80 | (index - startPacket);
//		        System.out.println("header: "+packetHeader + " index:"+index);
	    		dataOut.writeByte(packetHeader);
	    		writeBGRA(dataOut, data[index]);
	    		index++;
	    	}
    	} while (index < data.length);
    }
	// write everything backwards
    private static void writeShort(DataOutputStream dataOut, int v) throws IOException 
    {
    	dataOut.writeByte(v & 0xFF);
    	dataOut.writeByte((v >> 8) & 0xFF);
    }
    private static void writeBGRA(DataOutputStream dataOut, int v) throws IOException 
    {
    	dataOut.writeByte((v >> 16) & 0xFF);
    	dataOut.writeByte((v >> 8) & 0xFF);
    	dataOut.writeByte(v & 0xFF);
    	dataOut.writeByte((v >> 24) & 0xFF);	// ALPHA
    }
	
	public void drawAlphaRect(Graphics2D g)
	{
        g.drawImage(image, null, x, y);		
        g.setColor(Color.black);
        g.drawRect(x, y, getWidth()-1, getHeight()-1);
        g.setColor(Color.red);
        g.drawRect(x+xOffset, y+yOffset, opaqueWidth-1, opaqueHeight-1);
	}

    private boolean checkUpToDate(File[] files, String strOutFile)
    {
    	Long timeStamp;
    	Long max = new Long(0);
    	for (int n = 0; n < files.length; n++) 
    	{
    		timeStamp = files[n].lastModified();
    		if (timeStamp > max) max = timeStamp;
    	}
    	File outFile = new File(strOutFile);
    	if (max > outFile.lastModified()) return false;
    	strErr = "Atlas already created";
    	return true;
    }
	

	GLTexturePuzzle texturePuzzle;
	listSizePVR = new ArrayList<GLTexturePuzzle>();
	listSizePVR.add(new GLTexturePuzzle(32, 32, 1));
	listSizePVR.add(new GLTexturePuzzle(64, 64, 1));
	listSizePVR.add(new GLTexturePuzzle(128, 128, 1));
	listSizePVR.add(new GLTexturePuzzle(128, 128, 2));
	listSizePVR.add(new GLTexturePuzzle(128, 128, 3));
	listSizePVR.add(new GLTexturePuzzle(256, 256, 1));
	listSizePVR.add(new GLTexturePuzzle(256, 256, 2));
	listSizePVR.add(new GLTexturePuzzle(256, 256, 3));
	listSizePVR.add(new GLTexturePuzzle(512, 512, 1));
	listSizePVR.add(new GLTexturePuzzle(512, 512, 2));

	texturePuzzle = new GLTexturePuzzle(512, 512, 3);
	texturePuzzle.set(2, 256, 256);
	listSizePVR.add(texturePuzzle);

	listSizePVR.add(new GLTexturePuzzle(512, 512, 3));
	listSizePVR.add(new GLTexturePuzzle(1024, 1024, 1));

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 2);
	texturePuzzle.set(1, 256, 256);
	listSizePVR.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 2);
	texturePuzzle.set(1, 512, 512);
	listSizePVR.add(texturePuzzle);
	
	listSizeNormal = new ArrayList<GLTexturePuzzle>();
	listSizeNormal.add(new GLTexturePuzzle(32, 32, 1));
	listSizeNormal.add(new GLTexturePuzzle(64, 64, 1));
	listSizeNormal.add(new GLTexturePuzzle(128, 128, 1));
	listSizeNormal.add(new GLTexturePuzzle(256, 128, 1));
	listSizeNormal.add(new GLTexturePuzzle(128, 256, 1));
//	listSizeNormal.add(new GLTexturePuzzle(128, 128, 3));
	listSizeNormal.add(new GLTexturePuzzle(256, 256, 1));
	listSizeNormal.add(new GLTexturePuzzle(512, 256, 1));
	listSizeNormal.add(new GLTexturePuzzle(256, 512, 1));
//	listSizeNormal.add(new GLTexturePuzzle(256, 256, 3));
	listSizeNormal.add(new GLTexturePuzzle(512, 512, 1));
	listSizeNormal.add(new GLTexturePuzzle(1024, 512, 1));
	listSizeNormal.add(new GLTexturePuzzle(512, 1024, 1));

	texturePuzzle = new GLTexturePuzzle(1024, 512, 2);
	texturePuzzle.set(1, 256, 256);
	listSizeNormal.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 512, 2);
	texturePuzzle.set(1, 512, 256);
	listSizeNormal.add(texturePuzzle);
	
	texturePuzzle = new GLTexturePuzzle(1024, 512, 2);
	texturePuzzle.set(1, 512, 512);
	listSizeNormal.add(texturePuzzle);
	
	listSizeNormal.add(new GLTexturePuzzle(1024, 1024, 1));
	texturePuzzle = new GLTexturePuzzle(1024, 1024, 2);
	texturePuzzle.set(1, 256, 256);
	listSizeNormal.add(texturePuzzle);
	
	texturePuzzle = new GLTexturePuzzle(1024, 1024, 2);
	texturePuzzle.set(1, 512, 256);
	listSizeNormal.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 2);
	texturePuzzle.set(1, 512, 512);
	listSizeNormal.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 2);
	texturePuzzle.set(1, 1024, 512);
	listSizeNormal.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 3);
	texturePuzzle.set(1, 1024, 512);
	texturePuzzle.set(2, 256, 256);
	listSizeNormal.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 3);
	texturePuzzle.set(1, 1024, 512);
	texturePuzzle.set(2, 512, 256);
	listSizeNormal.add(texturePuzzle);
	
	texturePuzzle = new GLTexturePuzzle(1024, 1024, 3);
	texturePuzzle.set(1, 1024, 512);
	texturePuzzle.set(2, 512, 512);
	listSizeNormal.add(texturePuzzle);
	
	texturePuzzle = new GLTexturePuzzle(1024, 1024, 2);
	listSizeNormal.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 3);
	texturePuzzle.set(2, 256, 256);
	listSizeNormal.add(texturePuzzle);

	texturePuzzle = new GLTexturePuzzle(1024, 1024, 3);
	texturePuzzle.set(2, 512, 256);
	listSizeNormal.add(texturePuzzle);
	
	texturePuzzle = new GLTexturePuzzle(1024, 1024, 3);
	texturePuzzle.set(2, 512, 512);
	listSizeNormal.add(texturePuzzle);
	
	
	public boolean SplitImages(String strInDir)
	{
		strErr = "";
	    File dir = new File(strInDir);
	    File[] files = null;
	    FilenameFilter filter = new FilenameFilter() {
	        public boolean accept(File dir, String name) {
	            return name.endsWith("_split.png");
	        }
	    };
	    files = dir.listFiles(filter);
	    
	    int n, x, y, xCount, yCount;
	    int size = 256;
	    int width, height;
	    if (files == null || files.length == 0) return true; 
		BufferedImage imageIn;
	    String name ="";
	    try 
	    {
	        // Read from a file
	        for (n = 0; n < files.length; n++) 
	        {
	        	name = files[n].getName();
	    	    File file = new File(strInDir + "\\" + name);
	    	    imageIn = ImageIO.read(file);
	    	    y = 0;
	    	    yCount = 0;
	    	    do
	    	    {
	        	    if (y + size > imageIn.getHeight())
	        	    {
	        	    	height = imageIn.getHeight() - y;
	        	    }
	        	    else
	        	    {
	        	    	height = size;
	        	    }
	        	    x = 0;
	        	    xCount = 0;
		    	    do
		    	    {
		        	    if (x + size > imageIn.getWidth())
		        	    {
		        	    	width = imageIn.getWidth() - x;
		        	    }
		        	    else
		        	    {
		        	    	width = size;
		        	    }
		    	        BufferedImage imageOut = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
		    	        Graphics2D g = imageOut.createGraphics();
		    	    	g.drawImage(imageIn, 0, 0, width, height, x, y, x+width, y+height, null);
		    	    	x += width;
		    	    	xCount++;
			            String newName = strInDir + "\\" + name.substring(0, name.length()-10) + 
			            	xCount +"_" + yCount + ".png";
			    	    File fileOut;
			       		fileOut = new File(newName);
			       		ImageIO.write(imageOut, "png", fileOut);
			    	    g.dispose();
		    	    } while (x < imageIn.getWidth());
	    	    	y += height;
	        	    yCount++;
	    	    } while (y < imageIn.getHeight());
	    	    file.delete();
	        }
	    } catch (Exception e) 
	    {
	    	strErr = "error: "+name+": " +e.toString();
	        return false;
	    }
	    return true;
	}
	
    JLabel labelImageSize;
	labelImageSize = new JLabel();
	panelDisplay.add(labelImageSize);

	
	  //      long start = System.currentTimeMillis();
    
//  if (checkConvPVR.isSelected()) xx
//  int format = optionsDialog.getImageFormat();
//  int alphaThresh = optionsDialog.getAlphaThresh();

	public int getImageFormat()
	{
		int format;
	    if (btnPNG.isSelected())
	    	format = TexturePiece.PNG;
	    else if (btnTARGA_TRUE_COLOR.isSelected())
	    	format = TexturePiece.TARGA_TRUE_COLOR;
	    else 
			format = TexturePiece.TARGA16;
	    return format;
	}

	public enum ImageFormat
	{
		PNG (0, "PNG", "PNG"),
		TARGA_TRUE_COLOR(1, "TARGA_TRUE_COLOR", "32-bit Targa");
		TARGA16(2, "Targa16", "16-bit Targa");
		PVR(3, "PVR", "PVR");
		private final int index;
		private final String name;
		private final String nameLong;
	}
	for (Planet p : Planet.values())
	{

		public void loadSizes(String fileName)
		{
			try 
			{
				File fXmlFile = new File(fileName);
				DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
				Document doc = dBuilder.parse(fXmlFile);
	 			NodeList nodeList = doc.getElementsByTagName("textureRange");
				
				for (int n = 0; n < nodeList.getLength(); n++) 
				{
					Node node = nodeList.item(n);
					if (node.getNodeType() == Node.ELEMENT_NODE) 
					{	 
						Element element = (Element)node;
						if (element.getAttribute("type").equals("normal"))
						{
							rangeNormal.load(element);
						}
						else
						{
							rangeSquare.load(element);
						}
					}
				}	
			} catch (Exception e) 
			{
				e.printStackTrace();
			}		
		}
		atlasMaker.loadSizes("textureSizes.xml");
		private TextureRange rangeNormal, rangeSquare; 
		rangeNormal = new TextureRange(); 
		rangeSquare = new TextureRange();

	    private void Resize()
	    {
	    	// we already have created the textures
	    	labelError.setText("");
//	        int index = selectDisplay.getSelectedIndex();
//	        if (index == -1) return;
//	        int height = Integer.parseInt(textHeight.getText());
//	        int width = Integer.parseInt(textWidth.getText());
//	    	if (!listTexture.get(index).resize(width, height))
//	    	{
//	    		labelError.setText("images did not fit in "+width+"x"+height);
//	    		return;
//	    	}    		
//	    	
//	    	String strOutFile = textOutFile.getText();
//	        int pos = strOutFile.indexOf('.');
//	        if (strOutFile.length() < 8 || pos == -1)
//	        {
//	    		labelError.setText("Invalid Output file name");
//	        	return;
//	        }
//	    	String strOutDir = "";
//	    	String atlasName = "main";
//	        pos = strOutFile.lastIndexOf('\\');
//	        if (pos != -1)
//	        {
//	        	strOutDir = strOutFile.substring(0, pos+1);
//	        }
//	        String inDir = textInputDir.getText();
//	        pos = inDir.lastIndexOf('\\');
//	        if (pos != -1)
//	        {
//	        	atlasName = inDir.substring(pos+1);
//	        }
//	    	
//	    	listTexture.get(index).createStuff(strOutDir+atlasName, 
//	    			index, optionsDialog);
//	        changeImage(listTexture.get(index).getImageDisplay());
//	        WriteTXT();
	    }
	    
        else 
        {
        	if (event.getSource() instanceof JButton)
        	{
	        	JButton button = (JButton)event.getSource();
	        	if (button.getText() == "Resize")
	        		Resize();
        	}
        }

        JPanel panelResize = new JPanel();
        JButton btnResize = new JButton("Resize");
    	btnResize.addActionListener(this);
    	JTextField textHeight = new JTextField(3);
    	JTextField textWidth = new JTextField(3);
        panelResize.add(btnResize);
        panelResize.add(textWidth);
        panelResize.add(textHeight);		
    	textHeight.setText(""+imageIcon.getIconHeight());
    	textWidth.setText(""+imageIcon.getIconWidth());
        panelDisplay.add(panelResize);
        
    	public void getNextSizeFits(int pixelsReq, int maxWidth, int maxHeight)
    	{
            do
            {
        		int numPixels = getNumPixels();
            	if (pixelsReq <= numPixels && 
            			maxWidth <= getWidth(0) &&
            			maxHeight <= getHeight(0))
            	{
            		break;
            	}
            	int nextStep = pixelsReq;
                for (int n = 0; n < listSize.size()-1; n++) 
                {
                	numPixels -= listSize.get(n).getNumPixels();
                	nextStep -= listSize.get(n).getNumPixels();
                }
        		TextureSize lastTextureSize = listSize.get(listSize.size()-1);
            	if (nextStep > lastTextureSize.getMaxPixels()
            			&& !lastTextureSize.isMaxSize())
            		lastTextureSize.maximize();
            	else
            		incTextureSize();
            } while(true);
    	}
    	public void remove(ArrayList<AtlasImage> listImage, int ixStart, int ixEnd)
    	{
    		int ixImage, n;
    		boolean added;
    		for (ixImage = ixStart; ixImage < ixEnd; ixImage++)
    		{
    			for (n = 0; n < listBinPacker.size(); n++)
    			{
    				
    				if (listBinPacker.get(n).remove(listImage.get(ixImage))) 
    				{
    					break;
    				}
    			}
    		} 
    	}	
      
    	
    	public boolean forceResize(int width, int height)
    	{
            // sort by biggest image first
            Collections.sort(listImage, new CompareArea());
    		this.width = width;
    		this.height = height;
    		TreeNode treeNode = new TreeNode(0, 0, 
        			width, height, "");
    		
    		int ixImage;
    		for (ixImage = 0; ixImage < listImage.size(); ixImage++)
    		{
    			if (!treeNode.add(listImage.get(ixImage)))
    				return false;
    		} 
    		return true;
    	}	
    	double reduction = 1.0 - binPackerList.size()*0.25;
    	if (reduction < 0.2) reduction = 0.2;
    	pixelsReq = (int)(pixelsReq*reduction);

		// GL_UNSIGNED_SHORT_5_6_5 
		// blue is five bits
    	int blue = (v & 0xFF);
    	int blueNibble = blue >> 3;

    	int red = ((v >> 16) & 0xFF);
    	int redNibble = red >> 3;

    	// green is six bits
    	int green = ((v >> 8) & 0xFF);
    	int greenNibble = green >> 2;
    	int upperGreen = greenNibble >> 3;
    	int lowerGreen = greenNibble & 0x7;

    	dest[indexOut] = (byte)((redNibble << 4) | upperGreen); 
    	indexOut++;
    	dest[indexOut] = (byte)((blueNibble << 4) | lowerGreen); 
    	indexOut++;

    	{
    		// GL_UNSIGNED_SHORT_5_6_5 
    		// blue is five bits
        	int blue = (v & 0xFF);
        	int blueNibble = blue >> 3;
	
	    	int red = ((v >> 16) & 0xFF);
        	int redNibble = red >> 3;

        	// green is six bits
        	int green = ((v >> 8) & 0xFF);
        	int greenNibble = green >> 2;
        	int upperGreen = greenNibble >> 3;
        	int lowerGreen = greenNibble & 0x7;

        	dest[indexOut] = (byte)((blueNibble << 3) | lowerGreen); 
	    	indexOut++;
        	dest[indexOut] = (byte)((redNibble << 3) | upperGreen); 
	    	indexOut++;
    	}
    	
        

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The BSD License


Written By
Software Developer Astronautz
Spain Spain
After working in the software industry for many years, I've started my own games company that specialises in strategy games for mobile platforms.

Comments and Discussions