/*
 * Version Information:
 *
 * 0.01  19-Jul-2002     initial release Marc Schoenefeld
 * http://www.beauchamp.de/cbm
 */

import java.io.*; 
import java.awt.image.*;
import java.awt.*; 
import java.awt.Image;
import java.util.zip.*;

class BeauchampEncoder
{
    public static final boolean ENCODE_ALPHA = true;
    public static final boolean NO_ALPHA = false;
    public static final int FILTER_NONE = 0;
    public static final int FILTER_SUB = 1;
    public static final int FILTER_UP = 2;
    public static final int FILTER_LAST = 2;
    protected byte pngBytes[];
    protected byte priorRow[];
    protected byte leftBytes[];
    protected int width;
    protected int height;
    protected int bytePos;
    protected int maxPos;
    protected int hdrPos;
    protected int dataPos;
    protected int endPos;
    protected CRC32 crc;
    protected long crcValue;
    protected boolean encodeAlpha;
    protected int filter;
    protected int bytesPerPixel;
    protected int compressionLevel;
    public BeauchampEncoder()
    {
        this(null, false, 0, 0);
    }

    public BeauchampEncoder(BufferedImage bufferedimage)
    {
        this(bufferedimage, false, 0, 0);
    }

    public BeauchampEncoder(BufferedImage bufferedimage, boolean flag)
    {
        this(bufferedimage, flag, 0, 0);
    }

    public BeauchampEncoder(BufferedImage bufferedimage, boolean flag, int i)
    {
        this(bufferedimage, flag, i, 0);
    }

    public BeauchampEncoder(BufferedImage bufferedimage, boolean flag, int i, int j)
    {
    	crc = new CRC32();
        encodeAlpha = flag;
        setFilter(i);
        if(j >= 0 && j <= 9)
            compressionLevel = j;
        image = bufferedimage;
        
        //compressionLevel = j;
    }

    protected boolean establishStorageInfo()
    {
        wRaster = image.getRaster();
        int i = wRaster.getNumDataElements();
        tType = wRaster.getTransferType();
        if(tType == 0 && i == 4 || tType == 3 && i == 1)
            bytesPerPixel = encodeAlpha ? 4 : 3;
        else
        if(tType == 0 && i == 1)
        {
            bytesPerPixel = 1;
            encodeAlpha = false;
        } else
        {
            return false;
        }
        return true;
    }

    public byte[] pngEncode()
    {
        return pngEncode(encodeAlpha);
    }

    public byte[] pngEncode(boolean flag)
    {
        byte abyte0[] = {
            -119, 80, 78, 71, 13, 10, 26, 10
        };
        if(image == null)
            return null;
        width = image.getWidth(null);
        height = image.getHeight(null);
        image = image;
        if(!establishStorageInfo())
            return null;
        pngBytes = new byte[(width + 1) * height * 3 + 200];
        maxPos = 0;
        bytePos = writeBytes(abyte0, 0);
        hdrPos = bytePos;
        writeHeader();
        dataPos = bytePos;
        if(writeImageData())
        {
            writeEnd();
            pngBytes = resizeByteArray(pngBytes, maxPos);
        } else
        {
            pngBytes = null;
        }
        return pngBytes;
    }

    public void setImage(BufferedImage bufferedimage)
    {
        image = bufferedimage;
        pngBytes = null;
    }

    protected void writeHeader()
    {
        int i = bytePos = writeInt4(13, bytePos);
        bytePos = writeString("IHDR", bytePos);
        width = image.getWidth(null);
        height = image.getHeight(null);
        bytePos = writeInt4(width, bytePos);
        bytePos = writeInt4(height, bytePos);
        bytePos = writeByte(8, bytePos);
        if(bytesPerPixel != 1)
            bytePos = writeByte(encodeAlpha ? 6 : 2, bytePos);
        else
            bytePos = writeByte(3, bytePos);
        bytePos = writeByte(0, bytePos);
        bytePos = writeByte(0, bytePos);
        bytePos = writeByte(0, bytePos);
        crc.reset();
        crc.update(pngBytes, i, bytePos - i);
        crcValue = crc.getValue();
        bytePos = writeInt4((int)crcValue, bytePos);
    }

    protected boolean writeImageData()
    {
        int i = height;
        int j = 0;
        Deflater deflater = new Deflater(compressionLevel);
        ByteArrayOutputStream bytearrayoutputstream = new ByteArrayOutputStream(1024);
        DeflaterOutputStream deflateroutputstream = new DeflaterOutputStream(bytearrayoutputstream, deflater);
        if(bytesPerPixel == 1)
            writePalette((IndexColorModel)image.getColorModel());
        try
        {
            int k;
            for(; i > 0; i -= k)
            {
                k = Math.min(32767 / (width * (bytesPerPixel + 1)), i);
                byte abyte0[] = new byte[width * k * bytesPerPixel + k];
                if(filter == 1)
                    leftBytes = new byte[16];
                if(filter == 2)
                    priorRow = new byte[width * bytesPerPixel];
                byte abyte2[];
                int ai[];
                if(tType == 0)
                {
                    abyte2 = (byte[])wRaster.getDataElements(0, j, width, k, null);
                    ai = null;
                } else
                {
                    ai = (int[])wRaster.getDataElements(0, j, width, k, null);
                    abyte2 = null;
                }
                int l = 0;
                int j1 = 0;
                int i1 = 1;
                for(int l1 = 0; l1 < width * k; l1++)
                {
                    if(l1 % width == 0)
                    {
                        abyte0[l++] = (byte)filter;
                        i1 = l;
                    }
                    if(bytesPerPixel == 1)
                        abyte0[l++] = abyte2[j1++];
                    else
                    if(tType == 0)
                    {
                        abyte0[l++] = abyte2[j1++];
                        abyte0[l++] = abyte2[j1++];
                        abyte0[l++] = abyte2[j1++];
                        if(encodeAlpha)
                            abyte0[l++] = abyte2[j1++];
                        else
                            j1++;
                    } else
                    {
                        abyte0[l++] = (byte)(ai[j1] >> 16 & 0xff);
                        abyte0[l++] = (byte)(ai[j1] >> 8 & 0xff);
                        abyte0[l++] = (byte)(ai[j1] & 0xff);
                        if(encodeAlpha)
                            abyte0[l++] = (byte)(ai[j1] >> 24 & 0xff);
                        j1++;
                    }
                    if(l1 % width == width - 1 && filter != 0)
                    {
                        if(filter == 1)
                            filterSub(abyte0, i1, width);
                        if(filter == 2)
                            filterUp(abyte0, i1, width);
                    }
                }

                deflateroutputstream.write(abyte0, 0, l);
                j += k;
            }

            deflateroutputstream.close();
            byte abyte1[] = bytearrayoutputstream.toByteArray();
            int k1 = abyte1.length;
            crc.reset();
            bytePos = writeInt4(k1, bytePos);
            bytePos = writeString("IDAT", bytePos);
            crc.update("IDAT".getBytes());
            bytePos = writeBytes(abyte1, k1, bytePos);
            crc.update(abyte1, 0, k1);
            crcValue = crc.getValue();
            bytePos = writeInt4((int)crcValue, bytePos);
            deflater.finish();
            return true;
        }
        catch(IOException ioexception)
        {
            System.err.println(ioexception.toString());
        }
        return false;
    }

    protected void writePalette(IndexColorModel indexcolormodel)
    {
        byte abyte0[] = new byte[256];
        byte abyte1[] = new byte[256];
        byte abyte2[] = new byte[256];
        byte abyte3[] = new byte[768];
        indexcolormodel.getReds(abyte0);
        indexcolormodel.getGreens(abyte1);
        indexcolormodel.getBlues(abyte2);
        for(int i = 0; i < 256; i++)
        {
            abyte3[i * 3] = abyte0[i];
            abyte3[i * 3 + 1] = abyte1[i];
            abyte3[i * 3 + 2] = abyte2[i];
        }

        bytePos = writeInt4(768, bytePos);
        bytePos = writeString("PLTE", bytePos);
        crc.reset();
        crc.update("PLTE".getBytes());
        bytePos = writeBytes(abyte3, bytePos);
        crc.update(abyte3);
        crcValue = crc.getValue();
        bytePos = writeInt4((int)crcValue, bytePos);
    }

        protected void filterSub(byte abyte0[], int i, int j)
    {
        int l = bytesPerPixel;
        int i1 = i + l;
        int j1 = j * bytesPerPixel;
        int k1 = l;
        int l1 = 0;
        for(int k = i1; k < i + j1; k++)
        {
            leftBytes[k1] = abyte0[k];
            abyte0[k] = (byte)((abyte0[k] - leftBytes[l1]) % 256);
            k1 = (k1 + 1) % 15;
            l1 = (l1 + 1) % 15;
        }

    }

    protected void filterUp(byte abyte0[], int i, int j)
    {
        int l = j * bytesPerPixel;
        for(int k = 0; k < l; k++)
        {
            byte byte0 = abyte0[i + k];
            abyte0[i + k] = (byte)((abyte0[i + k] - priorRow[k]) % 256);
            priorRow[k] = byte0;
        }

    }

    public int getCompressionLevel()
    {
        return compressionLevel;
    }

    public boolean getEncodeAlpha()
    {
        return encodeAlpha;
    }

    public int getFilter()
    {
        return filter;
    }


    protected byte[] resizeByteArray(byte abyte0[], int i)
    {
        byte abyte1[] = new byte[i];
        int j = abyte0.length;
        System.arraycopy(abyte0, 0, abyte1, 0, Math.min(j, i));
        return abyte1;
    }

    public void setCompressionLevel(int i)
    {
        if(i >= 0 && i <= 9)
            compressionLevel = i;
    }

    public void setEncodeAlpha(boolean flag)
    {
        encodeAlpha = flag;
    }

    public void setFilter(int i)
    {
        filter = 0;
        if(i <= 2)
            filter = i;
    }

    protected int writeByte(int i, int j)
    {
        byte abyte0[] = {
            (byte)i
        };
        return writeBytes(abyte0, j);
    }

    protected int writeBytes(byte abyte0[], int i)
    {
        maxPos = Math.max(maxPos, i + abyte0.length);
        if(abyte0.length + i > pngBytes.length)
            pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, abyte0.length));
        System.arraycopy(abyte0, 0, pngBytes, i, abyte0.length);
        return i + abyte0.length;
    }

    protected int writeBytes(byte abyte0[], int i, int j)
    {
        maxPos = Math.max(maxPos, j + i);
        if(i + j > pngBytes.length)
            pngBytes = resizeByteArray(pngBytes, pngBytes.length + Math.max(1000, i));
        System.arraycopy(abyte0, 0, pngBytes, j, i);
        return j + i;
    }

    protected void writeEnd()
    {
        bytePos = writeInt4(0, bytePos);
        bytePos = writeString("IEND", bytePos);
        crc.reset();
        crc.update("IEND".getBytes());
        crcValue = crc.getValue();
        bytePos = writeInt4((int)crcValue, bytePos);
    }

    protected int writeInt2(int i, int j)
    {
        byte abyte0[] = {
            (byte)(i >> 8 & 0xff), (byte)(i & 0xff)
        };
        return writeBytes(abyte0, j);
    }

    protected int writeInt4(int i, int j)
    {
        byte abyte0[] = {
            (byte)(i >> 24 & 0xff), (byte)(i >> 16 & 0xff), (byte)(i >> 8 & 0xff), (byte)(i & 0xff)
        };
        return writeBytes(abyte0, j);
    }

    protected int writeString(String s, int i)
    {
        return writeBytes(s.getBytes(), i);
    }


    protected BufferedImage image;
    protected WritableRaster wRaster;
    protected int tType;
}


public final class BeauchampKoala2PNG {

     final byte unused[] =new byte[2];
     final byte bitmap[] =new byte[8000];
     final byte mcolor[][] =new byte [25][40];
     final byte color[][] = new byte[25][40];
     final byte background[] =new byte[1]; 


    static final Color[] c64Colors = new Color[] {
	new Color(           0x00, 0x00, 0x00),
	new Color(     	     0xff, 0xff, 0xff),
	new Color(           0x88, 0x00, 0x00),
	new Color(           0xaa, 0xff, 0xee),
	new Color(           0xcc, 0x44, 0xcc),
	new Color(           0x00, 0xcc, 0x55),
	new Color(           0x00, 0x00, 0xaa),
	new Color(           0xee, 0xee, 0x77),
	new Color(           0xdd, 0x88, 0x55),
	new Color(           0x66, 0x44, 0x00),
	new Color(           0xff, 0x77, 0x77),
	new Color(           0x33, 0x33, 0x33),
	new Color(           0x77, 0x77, 0x77),
	new Color(           0xaa, 0xff, 0x66),
	new Color(           0x00, 0x88, 0xff),
	new Color(           0xbb, 0xbb, 0xbb)
};

    public static final int koala_width= 320;
    public static final int koala_height= 200;
    public static final int koala_colors=16;

    static byte low_nibble(byte x) {
	return (byte)((x) & 0x0f); 
    }

    static byte high_nibble(byte x) {
	return (byte )(((x) >> 4)  & 0x0f); 
    }

    byte getColor(byte row, byte col, int index) {
	//	System.err.println("row:"+row+"/col:"+col+"/index:"+index); 
	if (index == 0) {
	    return low_nibble(background[0]);
	}
	else if (index == 1) {
	    return high_nibble(mcolor[row][col]);
	}
	else if (index == 2) {
	    return low_nibble(mcolor[row][col]);
	}
	else 
	    return low_nibble(color[row][col]);
    }

    byte[] convertFile(String theFile) {
	try {
	int pixelDepth=24; 
	FileInputStream inFile = new FileInputStream(theFile);
	
	int read = inFile.read(unused); 
	read = inFile.read(bitmap); 

	for (int i = 0; i < 25; i++) {
	    read = inFile.read(mcolor[i]); 
	}

	for (int i = 0; i < 25; i++) {
	    read = inFile.read(color[i]); 
	}

	read = inFile.read(background); 
	
	BufferedImage myImage = 
	    new BufferedImage(
			      koala_width, 
			      koala_height, 
			      (pixelDepth == 8) ? BufferedImage.TYPE_BYTE_INDEXED :   BufferedImage.TYPE_4BYTE_ABGR );
	Graphics g;
	
	g = myImage.getGraphics();
	
	g.fillRect(0,0,koala_width,koala_height);
	
	int row = 0;
	int col = 0 ; 
	int p = 0; 
	for (row = 0; row < 200; row++) {
	    int row8 = row / 8; 
	

	    int pos = (row8) *320 + row % 8; 
	
	    int x  = 0;
	    byte index = 0; 
	    byte oldindex = -1; 
	    for (col = 0; col < 40; col++) {
		p = bitmap[pos]; 
		if (p < 0) {
		    p +=256;
		}

		for (int i = 0; i < 4; i++) {
		    //    System.err.println(">>"+p); 
		    oldindex = index;
		    index = getColor((byte)(row8), (byte)col, (p & 3));
		    //		    if (index != oldindex) {
			g.setColor(c64Colors[index]); 
			//		    }
		    g.drawLine(x+6-i*2,row,x+6-i*2+1,row);
		    p >>= 2;
		}
		
		x += 8;
		pos += 8;
	    }
	}

	int compressionLevel=9; 

	byte pngBytes[]; 
	int filter = 0; 	
	boolean encodeAlpha= false; 
	BeauchampEncoder png =  new BeauchampEncoder( 
					   myImage,
					   (encodeAlpha) ? BeauchampEncoder.ENCODE_ALPHA : BeauchampEncoder.NO_ALPHA,
					   filter, compressionLevel );
	pngBytes = png.pngEncode(); 

	return pngBytes; 
	
    }
    catch (Exception e) {
	e.printStackTrace(); 
	return null;

    }

    }

    public static void main (String[] args) {
    	try {
	if (args.length < 2) {
	    System.out.print( new String(new BeauchampKoala2PNG().convertFile(args[0])) ); 
	    System.exit(0);
	}
	else {
	    FileOutputStream outfile = new FileOutputStream(args[1]);
	    outfile.write(new BeauchampKoala2PNG().convertFile(args[0]) ); 
	    System.exit(0);
	    }
        }        
        catch (IndexOutOfBoundsException e) {
       	       	System.err.println("Parameters: [1] koa-file [2] png-file"); 
	       	System.err.println("http://www.beauchamp.de/cbm"); 
		System.err.println("You forgot to specify the filenames !"); 
        }
        catch (Exception e) {
       	       	System.err.println("Parameters: [1] koa-file [2] png-file"); 
	       	System.err.println("http://www.beauchamp.de/cbm"); 
		System.err.println("Some other exception occured!"); 
        	e.printStackTrace(System.err); 
        }

    }

}





