Storing image data in java files?

This is for the 4k competition.

I’m wondering if there is a way to store or define, let’s say a tiny 150 byte gif image that is 16x16 and 8 color (incl. transparency), IN JAVA?

I must say gif format compresses those kind of images amazingly well, 256 pixels in 8 different colors in just 150 bytes :slight_smile: I’m pretty sure I would need a whole lotta more of bytes to define that image in Java. Is there a trick for doing this or is it simply impossible?

The obvious solutions like putting it into a string or some array doesnt work (its bigger). You could try superpackme (use the search function). Well, if you only use 8 colors you could use some palettelized string with characters >0 and <0x7f (eg ‘a’ to ‘h’). Each pixel takes then only 1 byte and it gets zip compressed afterwards anyways.

[quote]The obvious solutions like putting it into a string or some array doesnt work (its bigger).
[/quote]
I just tested this. A simple “Hello World” class file has 369 bytes. Adding a 256 byte array (private byte[] image=new byte[]{ <256 values> }:wink: blows the class file up to 2035 bytes :frowning:

And how big was the class file without the byte array?

8 colors = 3 bits per pixel
256px * 3 = 768 bits = 96 bytes (= 12 longs…?)

I think you can use less than 96 bytes by drawing your image through code.

@Riven
I think he is after the smallest possible classfile size, which is dependent on the byte code javac produces to represent the image bytes.

@appel
without the byte[] it was 369bytes, so it needs 1666 bytes to represent 256 bytes in JVM byte code :frowning: it might be better to put the gif inside the jar (if the contest rules allow this) and use ImageIO.read(XYZ.class.getResource(“small.gif”))

I’ve discovered a method to compress my gif images from 2 kb down to mere ~200 bytes, and even strip away some Java code that does the getImage(), and hence maybe strip down those 2kb’s in my 4kb project down to 0kb.

I’ll simply define the images in a text file;
a) First line being the colors
b) All lines except 1st line will be pixels for seperate images.

Example, 4 color images.
Colors=0,0,0 234,234,123 94,38,123, 0,0,45 (R,G,B colors seperated by a whitespace character)
Image1=0032424312003242431200324243120032424312003242431200324243122341 (8x8 image, each decimal value represent a index in the color “table”)

Here’s how I would do it:
F03F8590CU2Z (I’ll be using the ascii value of those to represent numbers from 0-255)
0032424312003242431200324243120032424312003242431200324243122341
1210000012231023041023012302022020230031030401230123012303204012
0032424312003242431200324243120032424312003242431200324243122341
1210000012231023041023012302022020230031030401230123012303204012
0032424312003242431200324243120032424312003242431200324243122341
1210000012231023041023012302022020230031030401230123012303204012
0032424312003242431200324243120032424312003242431200324243122341
1210000012231023041023012302022020230031030401230123012303204012

Those are a few 8x8 4 colors images, if they were GIF they would take up a lot of space. But since that’s a text file, it will get compressed A LOT in the JAR file, I’ve tested this method and it takes 4 32x32 8 color images from 1,5k down to mere 180 bytes, and I’ll also save an overhead in the javacode since I only need to read a text file, and not images.

I’ve performed some more tests on this method, and it compresses one image just about 10-20%.
This method pays off when you’re storing multiple small low colored images in the same file.

Here are the results of a test:

Test #1
Number of images: 1
GIF image(s) size: 280 bytes
Output file size: 240 bytes

Test #2
Number of images: 2
GIF image(s) size: 790 bytes
Output file size: 340 bytes

Test #3
Number of images: 3
GIF image(s) size: 800 bytes
Output file size: 450 bytes

Here is an even simpler way I found to have simple images.


        int[] colors = new int[256];
        colors['*'] = 0xFFFFFFFF; // white

        byte[] data =  ("   ***   "+
                        " ******* "+
                        " ******* "+
                        "**  *  **"+
                        "**  *  **"+
                        "*********"+
                        "*** * ***"+
                        " ******* "+
                        " * * * * ").getBytes();


        BufferedImage image = g.getDeviceConfiguration().createCompatibleImage(9,9, Transparency.BITMASK);
        // or BufferedImage image = new BufferedImage(9,9); If you want to save space but don't care too much about speed.

        for(int i = 0;i<data.length;i++)
        {
            image['K'].setRGB(i%9,i/9,colors[data[i]]);            
        }

No need to load images or text files. No need to write complicated bit shifting code.
Just draw the image using what ever characters you like. Try to use the same ones for all images so they compress well.
Then set the color of each character. No need to set the transparent character color as if it is not initialized in the color array it will be set as 0 any way.

I wrote a utility to take a gif and produce the text data, but you can also eaisly just manually draw such simple images by hand.

The simplicity of the creation of the image plus letting the zip utility do all the work of compressing seams to win. At least for my images. Don’t forget to change the 9 to match what ever image size you need.

The example image is the skull from my LadyBug game.

I’ve actually found a new method that compresses the number of bytes used down to 1/3 from what was previously used in my idea, and that’s excluding the compression. (I Think!)

Could you send me the image generation tool you used for your ladybug game?

Here is the code for the image converter to use type java Convert image.gif. The converted file will be in image.gif.txt.
Just cut and past the code into your application. Let me know if you find improvements on the method.


import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.util.*;
import java.io.*;

public class Convert
{

    public static void main(String args[])
    {
        try
        {
            String colorMap = "*.@0123456789abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
            BufferedImage image;
            int color;
            Hashtable hash = new Hashtable();
            String s;
            image = ImageIO.read(Thread.currentThread().getContextClassLoader().getResource(args[0]));
            int width = image.getWidth();
            int height = image.getHeight();

            PrintWriter out = new PrintWriter(new FileWriter(args[0]+".txt"));

            for(int y = 0;y<height;y++)
            {
                if(y ==0)
                    out.print("data = (\"");
                else
                    out.print("        \"");
                for(int x = 0;x<width;x++)
                {
                    color = image.getRGB(x,y);
                    s = (String) hash.get(Integer.toHexString(color));
                    if(s == null)
                    {
                        if((color & 0xFF000000) == 0xFF000000)
                            s = ""+colorMap.charAt(hash.size());
                        else // use space for transparent pixels
                            s = " ";
                        hash.put(Integer.toHexString(color),s);
                    }

                    out.print(s);
                }
                if(y == height-1)
                    out.println("\").getBytes();");
                else
                    out.println("\"+");
                
            }

            out.println("int[] colors = new int[256];");
            Enumeration enumeration = hash.keys();
            while(enumeration.hasMoreElements())
            {
                String key = (String) enumeration.nextElement();
                out.println("colors['"+hash.get(key)+"'] = 0x"+key+";");
            }

            out.println("image = g.getDeviceConfiguration().createCompatibleImage("+width+","+height+", Transparency.BITMASK);");
            out.println("for(int i = 0;i<data.length;i++)");
            out.println("{");
            out.println("image.setRGB(i%"+width+",i/"+width+",colors[data[i]]);");            
            out.println("}");

            out.flush();
            out.close();
        }
        catch(Exception _ex) { }
    }
}


That’s not a good algorithm/way for what I’m trying to accomplish, I believe.

It took a 32x32 8 color GIF image of size 348 bytes and blew it up to 1,75kb txt file. And I tried zipping that text file, and it was around 700 bytes.

I want to take low-color tiny images (16x16, 32x32) and compact them so they’ll be a around 20% of the original gif size, once they’re packed into the jar file. External or internal (in the java code).

I’m gonna try and implement my idea, and see if it’s better.

No. You don’t zip the txt file it produces. You inline the code produced in your class file and compile it. Then zip it. You need to compare the size of that to the size of your text file plus the code needed to load the text file. Just trying to load a text file will use lots of bytes. My method does not need to load any file so there is very little code over head. The string that stores the image will get merged once compiled and should compact when ziped nicely. I created 12 8X5 images in about 50 bytes. The for loop to reconstruct the images will also get compressed with the other for loops in your code. You need to compare this method of loading images imbeded in your whole application with your method to see the real results.

Ok, point taken. I’ll still compare both (when i have time) and let you guys know.

Everything you have done thus far is just duplicating what JBanes’ SuperPackMe (and related) stuff does.

It is a simple operation to encode arbitrary binary files into a utf8 string.
However, due to the way the utf8 format works, your data will compress better if you store it in nibble (4bit) chunks.

If you want a more specialized image format to take advantage of known image format specifications; JBanes also has a very good system already outlined.
Ofcourse, only you can decide if the overhead of the additional code to decode the custom format outweigh the gains from said format.

I seriously suggest you take a look at SuperPackMe.

I did check out superpackme, but I just couldn’t figure out how to use it. Lacking some examples.

Ok, I tried superpackme, and it didn’t do anything for me. It reduced the size of my images by only 3%.

Well, I made a new compressor that compresses better than SuperPackMe.

Test 1

Input files: 9
Input file sizes (total): 1.668 bytes

SuperPackMe

  • Output file: 1.500 bytes
  • Size after gzip compression (size in jar): 1.251 bytes

My new method:

  • Output file: 2.226 bytes
  • Size after gzip compression (size in jar): 1.061 bytes

Difference: 1.251 -1.061 = 190 bytes chopped off the 4K JAR!

Test 2

Input files: 1 (9 sprites in 1 gif image, 16 colors) (I took those 9 images and put them into a 9 * 32x32 grid image.)
Input file size: 1.614 bytes

SuperPackMe

  • Output file: 5.811 bytes
  • Size after gzip compression (size in jar): 1.737 bytes

My new method:

  • Output file: 2.040 bytes
  • Size after gzip compression (size in jar): 1.096 bytes

Difference: 1.737-1.096 = 641 bytes chopped off the 4K JAR!

Conclusion

My new method always beats superpackme after the output file has been gzipped. And I believe the retrieval procedure also has a less overhead.
The drawback from my method is that it can only use 16 colors max. But in most cases, especially for 4K games, that is enough.

I don’t know why SuperPackMe packed the single image so badly, maybe it’s the overhead of having only a single image in one file?