TinyGame Resources

No good reason, just that I didn’t know System.arraycopy existed, I’m still a n00b you see. :stuck_out_tongue: I searched for the Array class for such a function, but there wasn’t one I could see so I gave up then and made my silly for loops.

Galaxy613; try java.util.Arrays; it has several handy functions on arrays, in all appropriate type variations.

Finished the first cut of my tiny paint program here. The image and a 16 colour palette are encoded in tightly packed base64 strings, to use in a game just copy the string from the text box and use the below function to decode. The palette array argument will be filled with the palette that was used to generate the image if it is not null.



	public static TinyImage decode64(String src, int[] palette){
		return decode(Base64.decodeBase64(src),palette);
	}
	
	public static TinyImage decode(byte[] src, int[] palette){
		if (src.length < 17){
			// must be at least 17 bytes for a 1x1 pixel image;
			return null;
		}
		
		int width = ((src[0]&0xFF) << 4) | ((src[1]&0xFF) >> 4);
		int height = ((src[1]&0xF) << 8) | (src[2]&0xFF);
		boolean useTransparent = ((src[3] >> 4) & 1) == 1;
		int transparent = useTransparent ? (src[3] & 0xF) : -1;
		TinyImage img = new TinyImage(width,height,transparent);
		
		// row width in bytes 
		int rowWidth = (width+1)/2;
		if (src.length < (rowWidth*height + 16)){
			// don't even bother
			return null;
		}

		for(int row = 0; row < height; row++){
			for(int col = 0; col < width/2; col++){
				int val = src[row*rowWidth + col + 16]&0xFF;
				img.pixels[row*width + col*2] = (val >> 4);
				img.pixels[row*width + col*2 + 1] = (val & 0xF);
			}
		}
		
		if (palette != null){
			//decode the image palette
			palette[0] = (((src[4]&0xFF) >> 2)) & 0x3F;
			palette[1] = ((((src[4]&0xFF) << 4)) | ((src[5]&0xFF) >> 4 )) & 0x3F;
			palette[2] = ((((src[5]&0xFF) << 2)) | ((src[6]&0xFF) >> 6 )) & 0x3F;
			palette[3] = ((src[6]&0xFF)) & 0x3F;
			palette[4] = ((src[7]&0xFF) >> 2) & 0x3F;
			palette[5] = (((src[7]&0xFF) << 4) | ((src[8]&0xFF) >> 4 )) & 0x3F;
			palette[6] = (((src[8]&0xFF) << 2) | ((src[9]&0xFF) >> 6 )) & 0x3F;
			palette[7] = (src[9]&0xFF) & 0x3F;
			palette[8] = ((src[10]&0xFF) >> 2) & 0x3F;
			palette[9] = ((((src[10]&0xFF) << 4)) | ((src[11]&0xFF) >> 4 )) & 0x3F;
			palette[10] = ((((src[11]&0xFF) << 2)) | ((src[12]&0xFF) >> 6 )) & 0x3F;
			palette[11] = (src[12]&0xFF) & 0x3F;
			palette[12] = ((src[13]&0xFF) >> 2) & 0x3F;
			palette[13] = ((((src[13]&0xFF) << 4)) | ((src[14]&0xFF) >> 4 )) & 0x3F;
			palette[14] = ((((src[14]&0xFF) << 2)) | ((src[15]&0xFF) >> 6 )) & 0x3F;
			palette[15] = (src[15]&0xFF) & 0x3F;
		}
		
		return img;
	}


You can use any Base64 to byte[] decoder you like I wrote my own, it’s not particularly efficient but is pretty straight forward and could be useful for encoding other resources.


	private final static byte[] ALPHABET = {
	    (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G',
	    (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N',
	    (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 
	    (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z',
	    (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g',
	    (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n',
	    (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 
	    (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z',
	    (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 
	    (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_'
	  };
	
	public static String encodeBase64(byte[] bytes){
		// used 4 base64 chars (24 bits) to encode 3 bytes (24 bits)
		int[] block = new int[3];
		StringBuilder sb = new StringBuilder();
		for(int i = 0; i < bytes.length; i+=3){
			block[0] = bytes[i];
			block[1] = i < (bytes.length - 1) ? bytes[i+1] : 0;
			block[2] = i < (bytes.length - 2) ? bytes[i+2] : 0;
			sb.append(encodeBlock(block));
		}
		return sb.toString();
	}
	
	private static final int MASK = 0x3F;
	
	public static byte[] decodeBase64(String s){
		byte[] bytes = new byte[(s.length()/4)*3];
		int byteIndex = 0;
		int[] block = new int[3];
		int blockCount = s.length()/4; // 4 chars per 3 bytes.
		for(int i = 0; i < blockCount; i++){
			decodeBlock(s, i*4, block);
			bytes[byteIndex] = (byte)block[0];
			bytes[byteIndex + 1] = (byte)block[1];
			bytes[byteIndex + 2] = (byte)block[2];
			byteIndex += 3;
		}
		return bytes;
	}

	
	private static int decodeChar(char c){
		if (c >= 65 && c <= 90){
			return c - 65;
		} else if (c >= 97 && c <= 122){
			return c - 71;
		} else if (c >= 48 && c <= 57){
			return c + 4;
		} else if (c == '-'){
			return 62;
		} else if (c == '_'){
			return 63;
		} else {
			return -1;
		}
	}
	
	private static String encodeBlock(int[] bytes){
		return new String(
			new byte[]{
					ALPHABET[bytes[0] & MASK],
					ALPHABET[bytes[1] & MASK],
					ALPHABET[bytes[2] & MASK],
					ALPHABET[(((bytes[0] >> 6) & 3) | ((bytes[1] >> 4) & 0xC) | ((bytes[2] >> 2) & 0x30))],
			}
		);
		
	}
	
	
	private static void decodeBlock(String s, int offset, int[] dest){
		int b4 = decodeChar(s.charAt(offset + 3));
		for(int i = 0; i < 3; i++){
			dest[i] = decodeChar(s.charAt(offset + i)) | (((b4 >> i*2) & 3) << 6); 
		}
	}
	


The image format if anyone wants to make a compatible tool is below.


Header 16 bytes

     msb  lsb
0000 wwwwwwww   w = width (most significant bits are in lower bytes)
0001 wwwwhhhh   h = height
0002 hhhhhhhh   x = format (0 = no transparency, 1 = use transparent index)
0003 uuuxtttt   t = transparent index (not necessarily used)      
0004 aaaaaabb   a-p EGA paltte index of colour 0-15
0005 bbbbcccc   u = unused (set to zero)
0006 ccdddddd   
0007 eeeeeeff
0008 ffffgggg
0009 gghhhhhh
0010 iiiiiijj
0011 jjjjkkkk
0012 kkllllll
0013 mmmmmmnn
0014 nnnnoooo
0015 oopppppp
               
Data

0016 xxxx xxxx  raster data 2 pixels per byte scanning from top left
0017 xxxx xxxx  the most significant bits of each byte is the leftmost pixel
0018 xxxx 0000  end of each row is padded with 4 zero bits if it is odd
0020 xxxx xxxx  
.
.
.


How easy would it be to change that image loader for use with Markus’ EgaImage class? I think the only major difference is that he uses a Byte array and you use a Int array for the pixels it seems.

I’ll probably try to convert it soon either way I guess.

Yet another full palette intializer (linear form):


private static final int[] EGA_PALETTE;
 
static {
  int x = 0;
  int r;
  
  EGA_PALETTE  = new int[64];
  
  for (int i = 0; i < 64; i++) {
    r  = x & 0x30303;
    r |= r << 2;
    r |= r << 4;
    x += 0x1041;
    
    EGA_PALETTE[i] = r;
  }
}

Here’s the point of this post. Some example validation code, easy to convert stuff like histograms, etc.


/** 
 * Returns a packed bit array of the EGA colors used.
 * Zero indicates an invalid color was encountered. 
 */
public static final long colorUsage(int[] data, int len)
{
  long c = 0;
  int  x = 0;
  int  y = 0;
  int  d,r;
  
  // Assume all the color are valid and only
  // checks at the end of processing.
  // x - checks that the nibbles are R,G,B are the same
  // y - checks that each bit pair of R,G,B are the same
  // r - calculates current color index (linear form)
  // c - packed bit array of used EGA colors
  
  for(int i=0; i<len; i++) {
    d  = data[i];
    x |= (d ^ (d >>4));
    y |= (d ^ (d >>2));
    r  = d & 0x030303;
    r |= (r >>  6);
    r |= (r >> 12);
    c |= 1L << (r & 0x3f);
  }
  
  // Check if there were any invalid RGB values.
  if (((x|y) & 0xF0F0F) != 0)
    return 0;
  
  return c;
}

/** Returns true if the buffer contains only 16 or less EGA colors. */
public static final boolean isValidRGB(int[] data, int len)
{
  int count = Long.bitCount(colorUsage(data,len));
  
  return (count > 0 && count <= 16);
}

Pretty much the same stuff as above, but broken into component functions for reference:


/** Check if RGB is a valid EGA color. Ignores the upper byte. */
public static final boolean isValidRGB(int rgb)
{
  int x = (rgb ^ (rgb >> 4)); // each nibble must be the same
  int y = (rgb ^ (rgb >> 2)); // each bit pair must be the same
  
  return ((x|y) & 0xF0F0F) == 0;
}

/** Convert RGB value (assumed valid) into linear index.*/
public static final int RGBToIndex(int rgb)
{
  int r = rgb & 0x030303;
  
  r |= (r >>  6);
  r |= (r >> 12);
  
  return r & 0x3f;
}

/** Directly create an RGB from a linear index. */
public static final int indexToRGB(int r)
{
  // scatter the counter (bit pairs to each byte)
  r  = (r | (r << 6) | (r << 12)) & 0x030303;
  r |= r << 2;  // duplicate bit pairs
  r |= r << 4;  // duplicate nibbles
  
  return r;
}

Neato! :smiley:

That’s some creepy compressed obfuscated magic, I love it! =D