TinyGame Resources

EgaImage.java:

public class EgaImage {
	// Read only values
	public final int width, height;
	public final byte[] pixels; // (You can still change the contents of this! And you should!)

	public EgaImage(int width, int height) {
		this.width = width;
		this.height = height;
		pixels = new byte[width * height];
	}

	// Blits another EgaImage on top of this one, treating -1 as transparent.
	public void draw(EgaImage image, int xPos, int yPos) {
		// Calculate region
		int x1 = xPos;
		int x2 = xPos + image.width;
		int y1 = yPos;
		int y2 = yPos + image.height;

		// Clip to edges
		if (x1 < 0)
			x1 = 0;
		if (y1 < 0)
			y1 = 0;
		if (x2 > width)
			x2 = width;
		if (y2 > height)
			y2 = height;

		for (int y = y1; y < y2; y++) {
			// Calculate scanline offsets in pixel arrays
			int inOffset = -xPos + (y - yPos) * image.width;
			int outOffset = y * width;

			for (int x = x1; x < x2; x++) {
				byte in = image.pixels[inOffset + x];
				if (in >= 0) {
					pixels[outOffset + x] = in;
				}
			}
		}
	}
}

EgaScreen.java:

import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

public class EgaScreen extends EgaImage {
	// Static immutable EGA palette lookup table
	// See http://upload.wikimedia.org/wikipedia/en/d/df/EGA_Table.PNG
	private static final int[] EGA_PALETTE = new int[64];
	static {
		for (int i = 0; i < 64; i++) {
			int b = ((i >> 0) & 1) * 0xaa + ((i >> 3) & 1) * 0x55;
			int g = ((i >> 1) & 1) * 0xaa + ((i >> 4) & 1) * 0x55;
			int r = ((i >> 2) & 1) * 0xaa + ((i >> 5) & 1) * 0x55;
			EGA_PALETTE[i] = (r << 16) | (g << 8) | b;
		}
	}

	// Public mutable frame palette, fixed at 16 bytes
	// The default values are from http://upload.wikimedia.org/wikipedia/en/d/df/EGA_Table.PNG
	public final byte[] palette = { 0, 1, 2, 3, 4, 5, 20, 7, 56, 57, 58, 59, 60, 61, 62, 63 };

	private BufferedImage image;
	private int[] outPixels;

	public EgaScreen(int width, int height) {
		super(width, height);
		image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		outPixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
	}

	// Display this screen onto an AWT Graphics object, scaled to fit the provided width and height
	public void display(Graphics g, int width, int height) {
		for (int i = 0; i < 40 * 30; i++) {
			outPixels[i] = EGA_PALETTE[palette[pixels[i]]];
		}
		g.drawImage(image, 0, 0, width, height, null);
	}
}

And a test applet:

EgaTest.java:

import java.applet.Applet;
import java.awt.Graphics;
import java.util.Random;

public class EgaTest extends Applet implements Runnable {
	private EgaScreen screen = new EgaScreen(40, 30);

	private boolean running;

	public void start() {
		running = true;
		new Thread(this).start();
	}

	public void stop() {
		running = false;
	}

	public void run() {
		Random random = new Random();

		EgaImage background = new EgaImage(40, 30);
		EgaImage image = new EgaImage(32, 32);
		for (int x = 0; x < 32; x++) {
			for (int y = 0; y < 32; y++) {
				double xd = (x / 31.0) * 2 - 1;
				double yd = (y / 31.0) * 2 - 1;
				double dist = Math.sqrt(xd * xd + yd * yd);
				if (dist < 1) {
					image.pixels[x + y * 32] = (byte) (random.nextDouble() + dist);
				} else {
					image.pixels[x + y * 32] = -1;
				}
			}
		}

		while (running) {
			for (int i = 0; i < 100; i++)
				background.pixels[random.nextInt(40 * 30)] = (byte) random.nextInt(16);

			screen.draw(background, 0, 0);

			double time = System.currentTimeMillis() / 100.0;
			int xBall = (int) (Math.sin(time) * 20);
			int yBall = (int) (Math.cos(time * 1.2) * 20);
			screen.draw(image, xBall - 16 + 20, yBall - 16 + 15);

			Graphics g = getGraphics();
			screen.display(g, getWidth(), getHeight());
			g.dispose();

			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

You should probably change the title to TinyGame Resources, may as well keep it all in one thread. I have started my framework as well and it is very close to yours except I called the classes TinyImage, TinyScreen and TinyGame. I went for an int array of pixels and an explicit transparent index so I can base 64 encode images into strings using 4bits per pixel. It drops the colours to 15 per image with transparency but I don’t think that is an issue.

Here is the code I use for updating the screen BufferedImage using an explicit integer upScale factor.


	private void updateScreenBuffer(){
		int[] pixels = ((DataBufferInt)screenBuffer.getRaster().getDataBuffer()).getData();
		for(int x = 0; x < WIDTH; x++){
			for(int y = 0; y < HEIGHT; y++){
				int rgb = EGA_COLORS[palette[buffer.pixels[y*WIDTH + x]]];
				for(int i = 0; i < upScale*upScale; i++){
					pixels[(y*upScale + i%upScale)*upScale*WIDTH + (x*upScale + i/upScale)] = rgb;
				}
			}
		}
	}


Thread renamed, good idea!

I finally got rid of my hex value array and wrote my own formula for it. I screwed up the order in the process but it doesn’t really matter.


public static final Color[] COLORS = new Color[64];

static {
	for(int i = 0, j = 0; i < COLORS.length; i++){
		if((i & 15) == 0){
			j = (i >> 4) << 8;
		}
		
		COLORS[i] = new Color(21760*(j+((i >> 2) & 3))+(i & 3)*85);
	}
}

Spent a long time getting it optimized/shrunk but it works.

Here’s an image to use as a reference for the array indexes: http://i41.tinypic.com/fk03h0.png

This is a half-finished framework I stiched together when I started on my entry.

EGAColor


package egalib.ega;

import java.awt.Color;

public final class EGAColor
{
private static final int[] HEX_VALUES = new int[]{
0x000000, 0x0000aa, 0x00aa00, 0x00aaaa, 0xaa0000, 0xaa00aa, 0xaaaa00, 0xaaaaaa,
0x000055, 0x0000ff, 0x00aa55, 0x00aaff, 0xaa0055, 0xaa00ff, 0xaaaa55, 0xaaaaff,
0x005500, 0x0055aa, 0x00ff00, 0x00ffaa, 0xaa5500, 0xaa55aa, 0xaaff00, 0xaaffaa,
0x005555, 0x0055ff, 0x00ff55, 0x00ffff, 0xaa5555, 0xaa55ff, 0xaaff55, 0xaaffff,
0x550000, 0x5500aa, 0x55aa00, 0x55aaaa, 0xff0000, 0xff00aa, 0xffaa00, 0xffaaaa,
0x550055, 0x5500ff, 0x55aa55, 0x55aaff, 0xff0055, 0xff00ff, 0xffaa55, 0xffaaff,
0x555500, 0x5555aa, 0x55ff00, 0x55ffaa, 0xff5500, 0xff55aa, 0xffff00, 0xffffaa,
0x555555, 0x5555ff, 0x55ff55, 0x55ffff, 0xff5555, 0xff55ff, 0xffff55, 0xffffff
};

public static final EGAColor[] EGA_COLORS = new EGAColor[64];

static
{
    for (int x=0; x<64; x++)
    {
        EGA_COLORS[x] = new EGAColor(x);
    }
}

public final int index;

public final Color awtColor;

public EGAColor(int index)
{
    this.index=index;
    this.awtColor = new Color(HEX_VALUES[index]);
}

}


EGAGraphics


package egalib.ega;

import java.awt.*;

public final class EGAGraphics
{
private EGAColor[] palette = new EGAColor[16];

private Graphics awt;

private int cIndex;

private static final Font 
    F_BIG = new Font("SansSerif",Font.PLAIN,10),
    F_MED = new Font("SansSerif",Font.PLAIN,8),
    F_SMALL = new Font("SansSerif",Font.PLAIN,6);
    
public static final byte BIG = 1, MED = 2, SMALL = 3;

public EGAGraphics(Graphics g)
{
    awt=g;
    for (int x=0; x<16; x++)
    {
        palette[x] = EGAColor.EGA_COLORS[x];
    }
    cIndex=0;
    awt.setColor(palette[cIndex].awtColor);
    awt.setFont(F_SMALL);
}

public void updatePalette(EGAColor color, int index)
{
    palette[index]=color;
    if (cIndex==index)
        awt.setColor(palette[index].awtColor);
}

public void setPalette(EGAColor[] palette)
{
    if (palette.length!=16)
        throw new RuntimeException("Palette must be an array of length 16!");
    for (int x=0; x<16; x++)
    {
        if (palette[x]==null)
            throw new NullPointerException("EGAColor in palette was null!");
        this.palette[x]=palette[x];
    }
}

public void setColor(int index)
{
    cIndex=index;
    awt.setColor(palette[index].awtColor);
}

public void drawRect(int x, int y, int w, int h)
{
    awt.drawRect(x,y,w,h);
}

public void fillRect(int x, int y, int w, int h)
{
    awt.fillRect(x,y,w,h);
}

public void drawString(String string, int x, int y)
{
    awt.drawString(string,x,y);
}

public void drawShape(Shape shape)
{
    ((Graphics2D)awt).draw(shape);
}

public void fillShape(Shape shape)
{
    ((Graphics2D)awt).fill(shape);
}

public void fontSize(byte _enum)
{
    switch (_enum)
    {
        case BIG:
            awt.setFont(F_BIG);
            break;
        case SMALL:
            awt.setFont(F_SMALL);
            break;
        case MED:
            awt.setFont(F_MED);
            break;
    }
}

}


EGADisplay


package egalib.ega;

import java.applet.Applet;
import java.awt.;
import java.awt.image.
;
import java.awt.geom.*;

public final class EGADisplay
{
private Applet applet;

private Canvas canvas;

private BufferedImage buffer;

private EGAGraphics graphics;

public EGADisplay(Applet applet)
{
    this.applet=applet;
    
    applet.setIgnoreRepaint(true);
    applet.setLayout(new BorderLayout());
    
    GraphicsConfiguration config =
        GraphicsEnvironment.getLocalGraphicsEnvironment().
        getDefaultScreenDevice().getDefaultConfiguration();
    canvas = new Canvas(config);
    canvas.setIgnoreRepaint(true);
    applet.add(canvas,BorderLayout.CENTER);
    buffer = config.createCompatibleImage(40,30,Transparency.OPAQUE);
    canvas.requestFocus();
    graphics = new EGAGraphics(buffer.getGraphics());
}

public EGAGraphics getGraphics()
{
    return graphics;
}

public void show()
{
    Graphics2D g = (Graphics2D)canvas.getGraphics();
    g.setTransform(AffineTransform.getScaleInstance(canvas.getWidth()/buffer.getWidth(),canvas.getHeight()/buffer.getHeight()));
    g.drawImage(buffer,0,0,null);
}

public void attachInput(EGAInput input)
{
    canvas.addKeyListener(input);
    canvas.addMouseListener(input);
}

}


EGAInput


package egalib.ega;

import java.awt.event.*;

public final class EGAInput implements KeyListener, MouseListener
{
private boolean[] keys = new boolean[256];
private boolean[] mouse = new boolean[4];
private int mx,my;

public int mouseX()
{
    return mx;
}

public int mouseY()
{
    return my;
}

public boolean mouseFocus()
{
    return mouse[3];
}

public boolean mouseDown(int button)
{
    return mouse[button-1];
}

public boolean keyDown(int key)
{
    return keys[key];
}

public void keyPressed(KeyEvent event)
{
    int code = event.getKeyCode();
    if (code<256)
        keys
=true;
    }
    
    public void keyReleased(KeyEvent event)
    {
        int code = event.getKeyCode();
        if (code<256)
            keys

=false;
}

public void mousePressed(MouseEvent event)
{
    mouse[event.getButton()-1]=true;
    mx = event.getX();
    my = event.getY();
}

public void mouseReleased(MouseEvent event)
{
    mouse[event.getButton()-1]=false;
    mx = event.getX();
    my = event.getY();
}

public void mouseEntered(MouseEvent event)
{
    mouse[3]=true;
    mx = event.getX();
    my = event.getY();
}

public void mouseExited(MouseEvent event)
{
    mouse[3]=false;
    mx = event.getX();
    my = event.getY();
}

public void keyTyped(KeyEvent e){}
public void mouseClicked(MouseEvent e){}

}


EGAGame


package egalib.game;

import java.applet.Applet;
import egalib.ega.*;

public abstract class EGAGame extends Applet implements Runnable
{
protected volatile EGADisplay ega_display;

protected volatile EGAInput ega_input;

protected volatile Thread ega_thread;

public final void init()
{
    ega_display = new EGADisplay(this);
    ega_input = new EGAInput();
    ega_display.attachInput(ega_input);
    ega_load();
}

public final void start()
{
    ega_thread = new Thread(this);
    ega_thread.start();
}

public final void stop()
{
    ega_thread.interrupt();
    ega_thread = null;
}

public final void destroy()
{
    
}

public final void run()
{
    while (!ega_thread.isInterrupted())
    {
        ega_update();
        ega_paint(ega_display.getGraphics());
        ega_display.show();
        try
        {
            Thread.sleep(30);
        }
        catch (Exception e){}
    }
}

public abstract void ega_update();
public abstract void ega_paint(EGAGraphics g);
public abstract void ega_load();

}


Its not complete but maybe somebody can use it.


I added a section at the wiki for this compo, since I find it so hard to know what the latest version of things are in a forum.

Feel free to add latest versions of code and hints, techniques in there, but naturally keep the discussions here.

“Tiny game” wiki page

Let me know of any ideas for how to improve or better use the wiki.

Yet another framework :stuck_out_tongue:

EGAImage can be used where you would normally use a BufferedImage. You can draw on it as you would a BufferedImage, except that all colours are automatically converted into the EGA 64 colour palette.

You can store sprite Images in a suitable sized EGAImage. You can also use it for double buffering the screen. When double buffering the screen, use the paint() method to draw the EGAImage onto the provided graphics context. The paint method takes a scaling parameter, which allows the image to be scaled up as required. The paint method also enforces the 16 colour palette rule. If there are too many colours, the least frequent are changed to near neighbours, or failing that, black.

There are two code snippits: The first contains the EGAImage class and the second a test framework for the class.

If you are thinking this must be too slow, I get around 200fps on this Intel Core 2 Duo.

Edit: Added changing to nearest neighbour


/*
 * EGAImage class for TinyGame Competition
 *
 * Copyright: Alan Waddington 2010
 * Licence:   Public Domain
 * Warranty:  None
 *
 * All drawing on an EGAImage is converted to the EGA 64 Colour palette.
 *
 * An EGAImage can be used as a screen double buffer.  Use the paint method
 * to render the image using a palette of the 16 most common colours in the
 * image at the specified magnification.
 *
 * Any image colours that do not fit in the palette are converted to near
 * neighbours or failing that black. If black is not in the palette, the
 * least frequent palette colour is converted to black.
 *
 * Frame rate depends on image size and the number of colours used, but
 * should be 100fps to 200fps plus depending on processor speed.
 */


import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.DataBufferByte;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

/** EGAImage is a subclass of BufferedImage with the 64 colour EGA palette */
public class EGAImage extends BufferedImage {

    private final static int COLOURS = 64;  // Number of colours in EGA palette
    private final static int PALETTE = 16;  // Number of simultaneous colours
    private final static int BITS    = 6;   // Number of bits in palette index

    private static IndexColorModel model = null;        // EGA Colour Model

    private byte[]     data;                            // Image data
    private int[]      count   = new int[COLOURS];      // No. of each colour

    private int[]      parent  = new int[COLOURS];      // Colour Tree
    private int[]      lower   = new int[COLOURS];
    private int[]      higher  = new int[COLOURS];

    private boolean[]  palette = new boolean[COLOURS];  // Colour is in palette

    /** Create an image based on the 64 colours of the EGA palette */
    public EGAImage(int width, int height) {
        super(width, height, BufferedImage.TYPE_BYTE_INDEXED, EGAColourModel());
        data = ((DataBufferByte)getRaster().getDataBuffer()).getData();
    }

    /** Helper function to create a shared instance of the EGA palette */
    private static IndexColorModel EGAColourModel() {
        if (model==null) {
            byte[] red   = new byte[COLOURS];
            byte[] green = new byte[COLOURS];
            byte[] blue  = new byte[COLOURS];
            for (int i=0; i<COLOURS; i++) {
                red[i]   = (byte)(((i>>2)&1)*0xaa + ((i>>5)&1)*0x55);
                green[i] = (byte)(((i>>1)&1)*0xaa + ((i>>4)&1)*0x55);
                blue[i]  = (byte)(((i>>0)&1)*0xaa + ((i>>3)&1)*0x55);
            }
            model = new IndexColorModel(BITS, COLOURS, red, green, blue);
        }
        return model;
    }

    /** Verify that the image contains 16 colours maximum */
    public int countColors() {
        int i, colours = 0;
        for (i=0; i<COLOURS; i++)
            count[i] = 0;
        for (i=0; i<data.length; i++)
            count[data[i]]++;
        for (i=0; i<COLOURS; i++)
            if (count[i]>0)
                colours++;
        return colours;
    }

    /** Reduce the number of image colours to 16 maximum. */
    public boolean consolidate() {
        int i, node, lastnode;
        boolean excessColours = countColors()>PALETTE;
        if (excessColours) {

            // Initialise search
            for (i=0; i<COLOURS; i++) {
                parent[i] = lower[i] = higher[i] = -1;
                palette[i] = false;
            }

            // Constuct search tree
            for (i=1; i<COLOURS; i++) {
                node = 0;
                while (true) {
                    if (count[i]<count[node]) {
                        if (lower[node]==-1) {
                            lower[node] = i;
                            parent[i] = node;
                            break;
                        } else {
                            node = lower[node];
                        }
                    } else {
                        if (higher[node]==-1) {
                            higher[node] = i;
                            parent[i] = node;
                            break;
                        } else {
                            node = higher[node];
                        }
                    }
                }
            }

            // Traverse tree and put most frequent colours in the palette
            node = 0;
            lastnode = -1;
            i = 0;

            while (true) {
                if (lastnode==parent[node]) { // Down
                    if (higher[node]==-1) {
                        palette[node] = true;
                        i++;
                        if (i==PALETTE)
                            break;
                        if (lower[node]==-1) {
                            if (parent[node]==-1)
                                break;
                            else {
                                lastnode = node;
                                node = parent[node];
                            }
                        } else {
                            lastnode = node;
                            node = lower[node];
                        }
                    } else {
                        lastnode = node;
                        node = higher[node];
                    }
                } else if (lastnode==higher[node]) { // Right
                    palette[node] = true;
                    i++;
                    if (i==PALETTE)
                        break;
                    if (lower[node]==-1) {
                        if (parent[node]==-1)
                            break;
                        else {
                            lastnode = node;
                            node = parent[node];
                        }
                    } else {
                        lastnode = node;
                        node = lower[node];
                    }
                } else { // Left
                    if (parent[node]==-1)
                        break;
                    else {
                        lastnode = node;
                        node = parent[node];
                    }
                }
            }

            // Ensure black is in the palette
            if (!palette[0]) {
                palette[node] = false;
                palette[0] = true;
            }

            // Look for near neighbours
            for (i=0;i<data.length; i++) {
                int colour = data[i];
                if (!palette[colour]) {
                    if (palette[colour ^ 0x08])
                        data[i] ^= 0x08;
                    else if (palette[colour ^ 0x10])
                        data[i] ^= 0x10;
                    else if (palette[colour ^ 0x20])
                        data[i] ^= 0x20;
                    else
                        data[i] = 0; // Give up and mark it black
                }
            }
        }
        return excessColours;
    }

    /** Paint image at specified magnification */
    public void paint(Graphics g, int x, int y, int magnify) {
        consolidate();
        ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g.drawImage(this, x, y, magnify*getWidth(), magnify*getHeight(), null);
    }

}

The test framework:



/*
 * Test Package for EGAImage
 *
 * Copyright: Alan Waddington 2010
 * Licence:   Public Domain
 * Warranty:  None
 */

import javax.swing.JFrame;
import java.awt.*;
import java.util.Random;

public class EGAImageTest extends JFrame {

    private final static int MAGNIFY = 4;

    public EGAImageTest() {

        EGAImage ega = new EGAImage(40, 30);
        Random rand = new Random();
        Graphics  g;
        setSize(100+40*MAGNIFY, 100+30*MAGNIFY);
        setVisible(true);
        while (isVisible()) {

            // Benchmark EGAImage
            long start = System.nanoTime();

            // Draw on EGA Image using full 24bit colour
            g = ega.getGraphics();
            g.setColor(Color.BLACK);
            g.fillRect(0,0,40,30);
            for (int x=0; x<40; x++)
                for (int y=0; y<30; y++) {
                    g.setColor(new Color(rand.nextInt(256),
                        rand.nextInt(256), rand.nextInt(256)));
                    g.fillRect(x, y, 1, 1);
                }
            g.dispose();

            // Draw EGAImage to Screen
            g = getGraphics();
            g.setColor(Color.LIGHT_GRAY);
            g.fillRect(0, 0, 100+40*MAGNIFY, 100+30*MAGNIFY);
            System.out.println("EGA Colours requested ="+ega.countColors());
            ega.paint(g, 50, 50, MAGNIFY);
            System.out.println("EGA Colours used      ="+ega.countColors());
            
            // Benchmark EGAImage
            long stop = System.nanoTime();
            long fps = 1000000000l/(stop-start);
            g.setColor(Color.BLACK);
            g.drawString("FPS="+fps, 50, 80+30*MAGNIFY);
            g.dispose();
            try {
                Thread.sleep(100);
            } catch (Exception e) {}
        }
    }

    public static void main(String[] args) {
        new EGAImageTest();
        System.exit(0);
    }
}

EGAImage handling an image:

http://homepage.ntlworld.com/alan.d.waddington/images/EGAImageBird.png

The image loaded was

http://homepage.ntlworld.com/alan.d.waddington/images/bird.png

Rather redundant at this point but here is another framework. It has focus listening with a titlescreen and pause state also text rendering and sub-image drawing. Demo with 4 running at once here



public class TinyGame extends Applet implements FocusListener, Runnable, MouseListener {

	public static final int FRAMES_PER_SEC = 30;
	public enum GameState {
		TITLE_SCREEN,
		PAUSED,
		RUNNING
	}
	
	private TinyScreen screen;
	private Thread gameThread;
	private boolean done;
	private GameState state;

	// test game
	private Random rand = new Random();
	private TinyImage ball;
	private int ballX, ballY;
	private int ballVelX, ballVelY;
	
	public TinyGame(){
		setLayout(new BorderLayout());
	}
	
	public void init(){
		int upScale = getWidth()/TinyScreen.WIDTH;
		screen = new TinyScreen(upScale);
		screen.addFocusListener(this);
		state = GameState.TITLE_SCREEN;
		add(screen, BorderLayout.CENTER);
		screen.addMouseListener(this);
		gameThread = new Thread(this);
		gameThread.start();

		// test game
		ball = new TinyImage(7,7,0);
		for(int i = 0; i < ball.width; i++){
			for(int j = 0; j < ball.height; j++){
				int dx = i - 3;
				int dy = j - 3;
				if (dy*dy + dx*dx <= 9){
					ball.setPixel(i, j, 1);
				}
			}	
		}
		ball.setPixel(2, 2, 15);
		ball.setPixel(2, 3, 15);
		ball.setPixel(3, 2, 15);
		ballVelX = 2;
		ballVelY = 1;
		ballX = 10;
		ballY = 12;
	}

	public void start(){
		if (state == GameState.PAUSED){
			state = GameState.RUNNING;
		}
	}
	
	public void stop(){
		if (state == GameState.RUNNING){
			state = GameState.PAUSED;
		}
	}
	
	public void destroy(){
		done = true;
		try {
			gameThread.join();
		} catch (InterruptedException e) {}
	}

	public void focusGained(FocusEvent event) {
		start();
	}

	public void focusLost(FocusEvent event) {
		stop();
	}
	
	public void stepGame(){
		if (ballX < 4 || ballX > 36){
			ballVelX = -ballVelX;
		}
		if (ballY < 4 || ballY > 26){
			ballVelY = -ballVelY;
		}
		ballX += ballVelX;
		ballY += ballVelY;
	}

	public void drawGame(){
		screen.fill(0);
		screen.setPixel(rand.nextInt(40), rand.nextInt(30), rand.nextInt(16));
		screen.drawImage(ball,ballX - 3,ballY - 3);
		screen.drawString("test", 12,12,2);
		screen.show();
	}
	
	public void drawTitle(){
		screen.fill(0);
		screen.drawString("click to", 4, 8, 3);
		screen.drawString("  play", 4, 15, 4);
		screen.show();
	}
	
	public void drawPause(){
		screen.fill(0);
		screen.drawString("paused", 8, 12, 3);
		screen.show();
	}

	public void run() {
		while(!done){
			long delta = System.nanoTime();
			synchronized (this){
				switch(state){
				case RUNNING:
					stepGame();
					drawGame();
					delta = System.nanoTime() - delta;
					delta /= 1000000; // to milisecs.
					break;
				case PAUSED:
					delta = 0;
					drawPause();
					break;
				case TITLE_SCREEN:
					delta = 0;
					drawTitle();
					break;
				}
			}
			// try to lock us at 30fps.
			int targetDelta = 1000/FRAMES_PER_SEC;
			if (delta < targetDelta){
				try {
					Thread.sleep(targetDelta - (int)delta);
				} catch (InterruptedException e) {}
			}
		}
	}

	public void mouseClicked(MouseEvent me) {
		state = GameState.RUNNING;
		screen.requestFocusInWindow();
	}

	public void mouseEntered(MouseEvent me) {
	}

	public void mouseExited(MouseEvent me) {
	}

	public void mousePressed(MouseEvent me) {
	}

	public void mouseReleased(MouseEvent me) {
	}
	
}




public class TinyScreen extends Canvas {
	
	public static final int[] EGA_COLORS = new int[]{
			0xFF000000, 0xFF0000AA, 0xFF00AA00, 0xFF00AAAA, 0xFFAA0000, 0xFFAA00AA, 0xFFAAAA00, 0xFFAAAAAA,
			0xFF000055, 0xFF0000FF, 0xFF00AA55, 0xFF00AAFF, 0xFFAA0055, 0xFFAA00FF, 0xFFAAAA55, 0xFFAAAAFF,
			0xFF005500, 0xFF0055AA, 0xFF00FF00, 0xFF00FFAA, 0xFFAA5500, 0xFFAA55AA, 0xFFAAFF00, 0xFFAAFFAA,
			0xFF005555, 0xFF0055FF, 0xFF00FF55, 0xFF00FFFF, 0xFFAA5555, 0xFFAA55FF, 0xFFAAFF55, 0xFFAAFFFF,
			0xFF550000, 0xFF5500AA, 0xFF55AA00, 0xFF55AAAA, 0xFFFF0000, 0xFFFF00AA, 0xFFFFAA00, 0xFFFFAAAA,
			0xFF550055, 0xFF5500FF, 0xFF55AA55, 0xFF55AAFF, 0xFFFF0055, 0xFFFF00FF, 0xFFFFAA55, 0xFFFFAAFF,
			0xFF555500, 0xFF5555AA, 0xFF55FF00, 0xFF55FFAA, 0xFFFF5500, 0xFFFF55AA, 0xFFFFFF00, 0xFFFFFFAA,
			0xFF555555, 0xFF5555FF, 0xFF55FF55, 0xFF55FFFF, 0xFFFF5555, 0xFFFF55FF, 0xFFFFFF55, 0xFFFFFFFF
	};
	
	private static final int[] FONT = new int[]{
		0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
		0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x2000,0x17D9,
		0x2B6A,0x749A,0x772A,0x38A3,0x49ED,0x39CF,0x2ACE,0x12A7,0x7BEF,0x39AA,
		0x0410,0x1410,0x4454,0x0E38,0x1511,0x252A,0x0000,0x5BEA,0x3AEB,0x2A6A,
		0x3B6B,0x72CF,0x12CF,0x2B4E,0x5BED,0x3497,0x5AED,0x7249,0x5B7D,0x5FFD,
		0x2B6A,0x12EB,0x456A,0x5AEB,0x388E,0x2497,0x2B6D,0x256D,0x2FED,0x5AAD,
		0x24AD,0x72A7,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x6B50,
		0x3B59,0x6270,0x6B74,0x6750,0x25D6,0x3D50,0x5B59,0x2482,0x1482,0x5749,
		0x2492,0x5BF8,0x5B50,0x2B50,0x1758,0x4D70,0x12C8,0x1450,0x24BA,0x2B68,
		0x2568,0x7F68,0x54A8,0x3D68,0x77B8,0x0000,0x0000,0x0000,0x0000,0x0000,
		0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,
	};
	
	public static final int WIDTH = 40;
	public static final int HEIGHT = 30;
	
	private final BufferedImage screenBuffer;
	private final int upScale;
	public final int[] palette;
	public final TinyImage buffer;
	
	public TinyScreen(int upScale){
		screenBuffer = new BufferedImage(WIDTH*upScale,HEIGHT*upScale,BufferedImage.TYPE_INT_RGB);
		buffer = new TinyImage(WIDTH,HEIGHT);
		palette = new int[]{0,1,2,3,4,5,20,7,56,57,58,59,60,61,62,63};
		this.upScale = upScale;
		setFocusable(true);
		setIgnoreRepaint(true);

	}
	
	public void setPixel(int x, int y, int value){
		buffer.setPixel(x,y,value);
	}

	private void updateScreenBuffer(){
		int[] pixels = ((DataBufferInt)screenBuffer.getRaster().getDataBuffer()).getData();
		for(int x = 0; x < WIDTH; x++){
			for(int y = 0; y < HEIGHT; y++){
				int rgb = EGA_COLORS[palette[buffer.pixels[y*WIDTH + x]]];
				for(int i = 0; i < upScale*upScale; i++){
					pixels[(y*upScale + i%upScale)*upScale*WIDTH + (x*upScale + i/upScale)] = rgb;
				}
			}
		}
	}
	
	public void show(){
		BufferStrategy bs = getBufferStrategy();
		if (bs == null){
			createBufferStrategy(2);
			bs = getBufferStrategy();
		}
		Graphics g = bs.getDrawGraphics();
		updateScreenBuffer();
		g.drawImage(screenBuffer,0,0,null);
		g.dispose();
		bs.show();
	}

	public void fill(int value) {
		Arrays.fill(buffer.pixels, value);
	}
	
	public void drawString(String s, int x, int y, int color){
		for(int i = 0; i < s.length(); i++){
			int value = FONT[s.charAt(i) - 28];
			for(int j = 0; j < 3*5; j++){
				int px = x + j%3 + i*4;
				int py = y + j/3;
				if (((1 << j) & value) != 0 && px < WIDTH && py < HEIGHT && px >= 0 && py >= 0){
					setPixel(px, py, color);
				}
			}
		}
	}
	
	public void drawImage(TinyImage src, int dx, int dy){
		drawImage(src,dx,dy,src.width,src.height,0,0);
	}
	
	public void drawImage(TinyImage src, int dx, int dy, int w, int h, int sx, int sy){
		int x1 = Math.max(0,dx);
		int x2 = Math.min(buffer.width-1, dx + w);
		int y1 = Math.max(0,dy);
		int y2 = Math.min(buffer.height-1, dy + h);
		w = x2 - x1;
		h = y2 - y1;
		int transparent = src.getTransparentIndex();
		if (w > 0 && h > 0){
			for(int row = y1; row < y2; row++){
				for(int col = x1; col < x2; col++){
					int val = src.pixels[((row - dy) + sy)*src.width + ((col - dx) + sx)];
					if (val != transparent){
						buffer.pixels[row*buffer.width + col] = val;
					}
				}
			}
		}
	}
}


public class TinyImage {

	public final int width;
	public final int height;
	public final int[] pixels;
	private int transparentIndex;
	
	public TinyImage(int width, int height, int transparentIndex){
		this.width = width;
		this.height = height;
		this.pixels = new int[width*height];
	}

	public TinyImage(int width, int height){
		this(width,height,-1);
	}
	
	public void setPixel(int x, int y, int value){
		pixels[y*width + x] = value;
	}
	
	public int getTransparentIndex(){
		return transparentIndex;
	}
}

Awesome Shannon, I’ll update my applet right now to add all those nice features.

If anyone else is a super-n00b and want to get in on this keyboard-input action, here is my SimpleKeyboard.java file:

// Honestly, this class borrowed heavily from a GameDev.net article.
// I couldn't think of a way to make it simpler without being too restrictive.

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

public class SimpleKeyboard implements KeyListener {
	private static final int KEY_COUNT = 256;
	private enum KeyState {
		RELEASED,
		PRESSED,
		ONCE
	}
	
	private boolean[] currentKeys = null;
	private KeyState[] keys = null;
	
	public SimpleKeyboard() {
		currentKeys = new boolean[ KEY_COUNT ];
		keys = new KeyState[KEY_COUNT];
		
		for (int i = 0; i < KEY_COUNT; i++)
			keys[i] = KeyState.RELEASED;
	}
	
	public synchronized void poll() { // Polls the system for input and updates based on received events.
		for (int i = 0; i < KEY_COUNT; i++) {
			if (currentKeys[i]) {
				if (keys[i] == KeyState.RELEASED)
					keys[i] = KeyState.ONCE;
				else
					keys[i] = KeyState.PRESSED;
			} else
				keys[i] = KeyState.RELEASED;
		}
	}
	
	public boolean keyDown( int keyCode ){
		return 	keys[ keyCode ] == KeyState.ONCE ||
        		keys[ keyCode ] == KeyState.PRESSED;
	}
	
	public boolean keyHit ( int keyCode ) {
		return	keys[keyCode] == KeyState.ONCE;
	}
	
	@Override
	public void keyPressed(KeyEvent e) {
		if ( e.getKeyCode() >= 0 && e.getKeyCode() < KEY_COUNT)
			currentKeys [e.getKeyCode()] = true;
	}

	@Override
	public void keyReleased(KeyEvent e) {
		if ( e.getKeyCode() >= 0 && e.getKeyCode() < KEY_COUNT)
			currentKeys [e.getKeyCode()] = false;
	}

	@Override
	public void keyTyped(KeyEvent e) {
		// TODO Auto-generated method stub

	}

}

You just need to make a SimpleKeyboard object and then use keyDown() and keyHit() methods while calling poll() during the main loop. The key codes are static ints of KeyEvent. (i.e. KeyEvent.VK_UP and so on)

I hope this helps some other guy starting out :3 The code is mostly copypasta from a GameDev.net with some of my own tweaks.

I’ve also been adding functions and making changes to Markus’ EgaImage class, for instance I added:

	public void fill(byte color){
		for (int x = 0; x < width; x++){
			for (int y = 0; y < height; y++){
				pixels[x + y*width] = color;
			}
		}
	}
	
	public void clear(){
		fill((byte)0);
	}

Here’s my TOTALLY AWESOME (not really :S) ToneSong class:

EDIT: I totally forgot! you NEED bobjob’s Tone.java file for this to work!!

// Bee Boop! Public Domain? Why not!

/* Use it like this:
 * 		ToneSong bkground = new ToneSong (	new int[][]{
				{185, 1900, 50}, // Freq, MS, Volume 100..0
				{-1,-1,-1},
				{185, 1900, 50},
				{-1,-1,-1},
				{185, 1900, 50},
				{-1,-1,-1},
				{185, 900, 50},
				{164, 900, 50}
		}, 1000); // Each beat is 1000 Millisecs
		bkground.setLoop(true);
		bkground.play();
 * then just use bkground.update(); in the main loop!
 */

public class ToneSong {
	private int[][] song;
	private int note = 0; // Current note
	private int pace; // In milliseconds
	private long lastMS;
	private boolean playing = false;
	private boolean loop = false;
	
	public ToneSong (int[][] song, int pace) {
		this.song = song;
		this.pace = pace;
		note = 0;
		lastMS = System.currentTimeMillis();
	}
	
	public void play() {
		setPlaying(true);
	}
	
	public void update(){
		if (!playing)
			return;
		
		long curMilli = System.currentTimeMillis();
		if (lastMS+pace <= curMilli) {
			if ( song[note][0] > 0 && 
					(song[note][2] >= 0 && song[note][2] <= 100) ) {
				Tone.sound(song[note][0], song[note][1], (double)(song[note][2])*0.01);
			}
			lastMS = curMilli;
			note++;
			if (note >= song.length){
				if (loop)
					setPlaying(true);
				else
					setPlaying(false);
			}
		}
	}

	public void setPlaying(boolean playing) {
		this.playing = playing;
		this.note = 0;
	}

	public boolean isPlaying() {
		return playing;
	}

	public void setLoop(boolean loop) {
		this.loop = loop;
	}

	public boolean isLoop() {
		return loop;
	}
}

Great for making crap-tastic musics.

In that case:
for those who want retro style sounds fx, generates tones at runtime, no sound resources are required.
example applet: Test Tone
source: Tone Source

simple to use you just make static call to the Tone class as follows:

Tone.sound(int tone,int time,double volume);

queue sound, for music and such:

Tone.soundQueue(int tone,int time,double volume);

A revision of my EGAImage class that also considers inter colour distance as well as colour frequency when choosing colours.


/*
 * EGAImage class for TinyGame Competition
 *
 * Copyright: Alan Waddington 2010
 * Licence:   Public Domain
 * Warranty:  None
 *
 * All drawing on an EGAImage is converted to the EGA 64 Colour palette.
 *
 * An EGAImage can be used as a screen double buffer.  Use the paint method
 * to render the image using a palette of the 16 most common colours in the
 * image at the specified magnification.
 *
 * Any image colours that do not fit in the palette are converted to near
 * neighbours.
 *
 * Frame rate depends on image size and the number of colours used, but
 * should be 100fps to 200fps plus depending on processor speed.
 */


import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.DataBufferByte;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

/** EGAImage is a subclass of BufferedImage with the 64 colour EGA palette */
public class EGAImage extends BufferedImage {

    private final static int COLOURS = 64;  // Number of colours in EGA palette
    private final static int PALETTE = 16;  // Number of simultaneous colours
    private final static int BITS    = 6;   // Number of bits in palette index
    private final static int PRIMARIES = 8;   // Number of priority colours

    private static IndexColorModel model = null;        // EGA Colour Model

    private byte[]     data;                            // Image data
    private int[]      count   = new int[COLOURS];      // No. of each colour

    private int[]      parent  = new int[COLOURS];      // Colour Tree
    private int[]      lower   = new int[COLOURS];
    private int[]      higher  = new int[COLOURS];

    private boolean[]  palette = new boolean[COLOURS];  // Colour is in palette
    private boolean[]  primary = new boolean[PRIMARIES];// Primary is used

    private int colours, primaries;                     // Colour counts

    /** Create an image based on the 64 colours of the EGA palette */
    public EGAImage(int width, int height) {
        super(width, height, BufferedImage.TYPE_BYTE_INDEXED, EGAColourModel());
        data = ((DataBufferByte)getRaster().getDataBuffer()).getData();
    }

    /** Helper function to create a shared instance of the EGA palette */
    private static IndexColorModel EGAColourModel() {
        if (model==null) {
            byte[] red   = new byte[COLOURS];
            byte[] green = new byte[COLOURS];
            byte[] blue  = new byte[COLOURS];
            for (int i=0; i<COLOURS; i++) {
                red[i]   = (byte)(((i>>2)&1)*0xaa + ((i>>5)&1)*0x55);
                green[i] = (byte)(((i>>1)&1)*0xaa + ((i>>4)&1)*0x55);
                blue[i]  = (byte)(((i>>0)&1)*0xaa + ((i>>3)&1)*0x55);
            }
            model = new IndexColorModel(BITS, COLOURS, red, green, blue);
        }
        return model;
    }

    /** Verify that the image contains 16 colours maximum */
    public int countColors() {
        int i;
        colours = 0;
        primaries = 0;
        for (i=0; i<COLOURS; i++)
            count[i] = 0;
        for (i=0; i<PRIMARIES; i++)
            primary[i] = false;
        for (i=0; i<data.length; i++) {
            count[data[i]]++;
            primary[data[i] & 0x07] = true;
        }
        for (i=0; i<COLOURS; i++)
            if (count[i]>0)
                colours++;
        for (i=0;i<PRIMARIES;i++)
            if (primary[i])
                primaries++;
        return colours;
    }

    /** Reduce the number of image colours to 16 maximum. */
    public boolean consolidate() {
        int i, node, lastnode;
        boolean excessColours = countColors()>PALETTE;
        if (excessColours) {

            // Initialise search
            for (i=0; i<COLOURS; i++) {
                parent[i] = lower[i] = higher[i] = -1;
                palette[i] = false;
            }

            // Constuct search tree
            for (i=1; i<COLOURS; i++) {
                node = 0;
                while (true) {
                    if (count[i]<count[node]) {
                        if (lower[node]==-1) {
                            lower[node] = i;
                            parent[i] = node;
                            break;
                        } else {
                            node = lower[node];
                        }
                    } else {
                        if (higher[node]==-1) {
                            higher[node] = i;
                            parent[i] = node;
                            break;
                        } else {
                            node = higher[node];
                        }
                    }
                }
            }

            // Traverse tree and put most frequent colours in the palette
            node = 0;
            lastnode = -1;
            colours = 0;

            while (true) {
                if (lastnode==parent[node]) { // Down
                    if (higher[node]==-1) {
                        if (primary[node & 0x07]) {
                            primary[node & 0x07] = false;
                            primaries--;
                        }
                        if (colours<PALETTE-primaries) {
                            palette[node] = true;
                            colours++;
                            if (colours==PALETTE)
                                break;
                        }
                        if (lower[node]==-1) {
                            lastnode = node;
                            node = parent[node];
                        } else {
                            lastnode = node;
                            node = lower[node];
                        }
                    } else {
                        lastnode = node;
                        node = higher[node];
                    }
                } else if (lastnode==higher[node]) { // Right
                    if (primary[node & 0x07]) {
                        primary[node & 0x07] = false;
                        primaries--;
                    }
                    if (colours<PALETTE-primaries) {
                        palette[node] = true;
                        colours++;
                        if (colours==PALETTE)
                            break;
                    }
                    if (lower[node]==-1) {
                        lastnode = node;
                        node = parent[node];
                    } else {
                        lastnode = node;
                        node = lower[node];
                    }
                } else { // Left
                    lastnode = node;
                    node = parent[node];
                }
            }

            // Look for near neighbours (Eye is least sensitive to blue)
            for (i=0;i<data.length; i++) {
                int colour = data[i];
                if (!palette[colour]) {
                    if (palette[colour ^ 0x08])         // Blue
                        data[i] ^= 0x08;
                    else if (palette[colour ^ 0x20])    // Red
                        data[i] ^= 0x20;
                    else if (palette[colour ^ 0x10])    // Green
                        data[i] ^= 0x10;
                    else if (palette[colour ^ 0x28])    // Blue and Red
                        data[i] ^= 0x28;
                    else if (palette[colour ^ 0x18])    // Blue and Green
                        data[i] ^= 0x18;
                    else if (palette[colour ^ 0x30])    // Red and Green
                        data[i] ^= 0x30;
                    else if (palette[colour ^ 0x38])    // All
                        data[i] ^= 0x38;
                    else
                        data[i] = (byte)node;           // Should never happen
                }
            }
        }
        return excessColours;
    }

    /** Paint image at specified magnification */
    public void paint(Graphics g, int x, int y, int magnify) {
        consolidate();
        ((Graphics2D)g).setRenderingHint(RenderingHints.KEY_INTERPOLATION,
                RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
        g.drawImage(this, x, y, magnify*getWidth(), magnify*getHeight(), null);
    }

}

(I realized I posted it in the wrong thread, so here we go again)

EgaCompressor:
Input: full color RGB
Output: indexed & dithered RGB (16 colors)

Executable JARs:
http://indiespot.net/files/ega-no-dithering.jar
http://indiespot.net/files/ega-dithering.jar

Webstartable JNLPs:
http://indiespot.net/files/ega-no-dithering.jnlp
http://indiespot.net/files/ega-dithering.jnlp

Sourcecode:


import java.util.Random;

public class EgaCompressor
{
   public static final int[]  RGB_COLORS    = new int[64];
   private static final int[] HISTOGRAM     = new int[64];
   private static final int[] PALETTE16     = new int[16];
   private static final int[] FROM_64_TO_16 = new int[64];

   static
   {
      for (int i = 0; i < 64; i++)
      {
         int r = (((i >> 4) & 3) * 0x55) << 16;
         int g = (((i >> 2) & 3) * 0x55) << 8;
         int b = (((i >> 0) & 3) * 0x55) << 0;
         RGB_COLORS[i] = r | g | b;
      }
   }

   public static void compress(int[] pixels)
   {
      squeezeTo64(pixels);
      squeeze64to16(pixels);
      extractFrom64(pixels);
   }

   private static Random random = new Random();

   private static void extractFrom64(int[] pixels)
   {
      for (int i = 0; i < pixels.length; i++)
      {
         int index64 = pixels[i];
         int r = (((index64 >> 4) & 3) * 0x55) << 16;
         int g = (((index64 >> 2) & 3) * 0x55) << 8;
         int b = (((index64 >> 0) & 3) * 0x55) << 0;
         pixels[i] = r | g | b;
      }
   }

   private static void squeezeTo64(int[] pixels)
   {
      for (int i = 0; i < pixels.length; i++)
      {
         int pixel = pixels[i];

         int r = (pixel >> 16) & 0xFF;
         int g = (pixel >> 8) & 0xFF;
         int b = (pixel >> 0) & 0xFF;

         int rOff = r / 0x55;
         int gOff = g / 0x55;
         int bOff = b / 0x55;

         // dithering
         rOff += (random.nextInt(0x55) - (r - rOff * 0x55)) >>> 31;
         gOff += (random.nextInt(0x55) - (g - gOff * 0x55)) >>> 31;
         bOff += (random.nextInt(0x55) - (b - bOff * 0x55)) >>> 31;

         pixels[i] = (rOff << 4) | (gOff << 2) | (bOff << 0);
      }
   }

   private static void squeeze64to16(int[] pixels)
   {
      // fill frequency table
      for (int i = 0; i < HISTOGRAM.length; i++)
      {
         HISTOGRAM[i] = 0;
      }
      for (int i = 0; i < pixels.length; i++)
      {
         HISTOGRAM[pixels[i]]++;
      }

      // find 16 most used colors
      for (int i = 0; i < 16; i++)
      {
         int maxCount = -1;
         int maxIndex = -1;
         for (int m = 0; m < HISTOGRAM.length; m++)
         {
            if (HISTOGRAM[m] > maxCount)
            {
               maxCount = HISTOGRAM[m];
               maxIndex = m;
            }
         }

         // add color to palette
         PALETTE16[i] = maxIndex;

         // clear entry
         HISTOGRAM[maxIndex] = 0;
      }

      // create lookup table from EGA64 to EGA16
      for (int i = 0; i < 64; i++)
      {
         // find nearest match in 16 color palette
         int r = (i >> 4) & 3;
         int g = (i >> 2) & 3;
         int b = (i >> 0) & 3;
         int minDiffIndex = -1;
         int minDiffValue = Integer.MAX_VALUE;
         for (int p = 0; p < PALETTE16.length; p++)
         {
            int rDiff = Math.abs(r - ((PALETTE16[p] >> 4) & 3));
            int gDiff = Math.abs(g - ((PALETTE16[p] >> 2) & 3));
            int bDiff = Math.abs(b - ((PALETTE16[p] >> 0) & 3));
            int nDiff = (rDiff + gDiff + bDiff);
            if (minDiffValue < nDiff)
            {
               continue;
            }
            minDiffValue = nDiff;
            minDiffIndex = p;
         }
         FROM_64_TO_16[i] = PALETTE16[minDiffIndex];
      }

      // convert 
      for (int i = 0; i < pixels.length; i++)
      {
         pixels[i] = FROM_64_TO_16[pixels[i]];
      }
   }
}

In my experiments, random dither works well at larger resolutions… at the lower 40x30, the illusion does not seem to be as effective :frowning: well for my purposes anyway :stuck_out_tongue:

To my eyes dithering looks bad at any resolution. I would rather go for a carefully chosen palette with colours that might be slightly off than dither.

More functions for Markus’ EgaImage class/framework:


	// Copies the pixels of the supplied EgaImage
	public void copyFrom(EgaImage img){
		if (!(pixels.length == img.pixels.length))
			return;
		for (int x = 0; x < width; x++){
			for (int y = 0; y < height; y++){
				pixels[x + y*width] = img.pixels[x + y*width];
			}
		}		
	}
	
	// Flips the image
	public void flip(boolean horiz, boolean verti) {
		if (horiz) {
			int halfwidth = width;
			
			if (width % 2 == 1) {
				halfwidth--;
			}
			halfwidth /= 2;
			
			for (int x = 0; x < halfwidth; x++){
				for (int y = 0; y < height; y++){
					byte tmpColor = pixels[x + y*width];
					pixels[x + y*width] = pixels[(width - x -1) + y*width];
					pixels[(width - x -1) + y*width] = tmpColor;
				}
			}
		}
		if (verti) {
			int halfheight = height;
			
			if (height % 2 == 1) {
				halfheight--;
			}
			halfheight /= 2;
			
			for (int x = 0; x < width; x++){
				for (int y = 0; y < halfheight; y++){
					byte tmpColor = pixels[x + y*width];
					pixels[x + y*width] = pixels[x + (height - y -1)*width];
					pixels[x + (height - y -1)*width] = tmpColor;
				}
			}
		}
	}

flip() probably isn’t completely optimized and what not, but it’s supposed to be used while setting up an image…

Galaxy, any reason you’re not using System.arraycopy in copyFrom?