Markus_Persson
JGO Kernel      Posts: 2092 Medals: 10
Mojang Specifications
|
 |
«
on:
2010-04-15 11:19:22 » |
|
EgaImage.java: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| public class EgaImage { public final int width, height; public final byte[] pixels; public EgaImage(int width, int height) { this.width = width; this.height = height; pixels = new byte[width * height]; }
public void draw(EgaImage image, int xPos, int yPos) { int x1 = xPos; int x2 = xPos + image.width; int y1 = yPos; int y2 = yPos + image.height;
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++) { 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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import java.awt.Graphics; import java.awt.image.BufferedImage; import java.awt.image.DataBufferInt;
public class EgaScreen extends EgaImage { 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 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(); }
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); } } |
|
|
|
|
Markus_Persson
JGO Kernel      Posts: 2092 Medals: 10
Mojang Specifications
|
 |
«
Reply #1 on:
2010-04-15 11:19:47 » |
|
And a test applet: EgaTest.java: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| 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(); } } } } |
|
|
|
|
ShannonSmith
Sr. Member   Posts: 365 Medals: 13
|
 |
«
Reply #2 on:
2010-04-15 11:35:24 » |
|
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. 1 2 3 4 5 6 7 8 9 10 11
| 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; } } } } |
|
|
|
|
|
Games published by our own members! Go get 'em!
|
|
Markus_Persson
JGO Kernel      Posts: 2092 Medals: 10
Mojang Specifications
|
 |
«
Reply #3 on:
2010-04-15 11:39:20 » |
|
Thread renamed, good idea!
|
|
|
|
jimsmi6
JGO n00b  Posts: 12
|
 |
«
Reply #4 on:
2010-04-15 20:39:22 » |
|
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. 1 2 3 4 5 6 7 8 9 10 11
| 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
|
|
|
|
|
Bob Dorian
JGO n00b  Posts: 2
|
 |
«
Reply #5 on:
2010-04-16 03:45:34 » |
|
This is a half-finished framework I stiched together when I started on my entry. EGAColor 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| [pre]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]); } } [/pre] |
EGAGraphics 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
| [pre]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; } } } [/pre] |
EGADisplay 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| [pre]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); } }[/pre] |
EGAInput 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| [pre]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[code]=true; } public void keyReleased(KeyEvent event) { int code = event.getKeyCode(); if (code<256) keys[code]=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){} } [/pre] |
EGAGame 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| [pre]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(); }[/pre] |
Its not complete but maybe somebody can use it.[/code][/code]
|
|
|
|
|
jojoh
JGO Ninja    Posts: 554 Medals: 6
games4j.com
|
 |
«
Reply #6 on:
2010-04-16 06:46:36 » |
|
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 pageLet me know of any ideas for how to improve or better use the wiki.
|
|
|
|
Alan_W
JGO Ninja    Posts: 734 Medals: 8
Java tames rock!
|
 |
«
Reply #7 on:
2010-04-16 15:52:42 » |
|
Yet another framework  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 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204
|
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;
public class EGAImage extends BufferedImage {
private final static int COLOURS = 64; private final static int PALETTE = 16; private final static int BITS = 6; private static IndexColorModel model = null; private byte[] data; private int[] count = new int[COLOURS]; private int[] parent = new int[COLOURS]; private int[] lower = new int[COLOURS]; private int[] higher = new int[COLOURS];
private boolean[] palette = new boolean[COLOURS]; public EGAImage(int width, int height) { super(width, height, BufferedImage.TYPE_BYTE_INDEXED, EGAColourModel()); data = ((DataBufferByte)getRaster().getDataBuffer()).getData(); }
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; }
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; }
public boolean consolidate() { int i, node, lastnode; boolean excessColours = countColors()>PALETTE; if (excessColours) {
for (i=0; i<COLOURS; i++) { parent[i] = lower[i] = higher[i] = -1; palette[i] = false; }
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]; } } } }
node = 0; lastnode = -1; i = 0;
while (true) { if (lastnode==parent[node]) { 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]) { 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 { if (parent[node]==-1) break; else { lastnode = node; node = parent[node]; } } }
if (!palette[0]) { palette[node] = false; palette[0] = true; }
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; } } } return excessColours; }
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); }
}
|
|
Time flies like a bird. Fruit flies like a banana.
|
|
|
Alan_W
JGO Ninja    Posts: 734 Medals: 8
Java tames rock!
|
 |
«
Reply #8 on:
2010-04-16 15:53:54 » |
|
The test framework: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
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()) {
long start = System.nanoTime();
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();
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()); 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); } }
|
|
Time flies like a bird. Fruit flies like a banana.
|
|
|
Alan_W
JGO Ninja    Posts: 734 Medals: 8
Java tames rock!
|
 |
«
Reply #9 on:
2010-04-16 18:06:24 » |
|
EGAImage handling an image:  The image loaded was 
|
Time flies like a bird. Fruit flies like a banana.
|
|
|
Games published by our own members! Go get 'em!
|
|
ShannonSmith
Sr. Member   Posts: 365 Medals: 13
|
 |
«
Reply #10 on:
2010-04-17 11:10:03 » |
|
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 here1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
| 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;
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();
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; break; case PAUSED: delta = 0; drawPause(); break; case TITLE_SCREEN: delta = 0; drawTitle(); break; } } 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) { } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| 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; } } } } } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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; } } |
|
|
|
|
|
Galaxy613
JGO n00b  Posts: 25
|
 |
«
Reply #11 on:
2010-04-17 11:33:45 » |
|
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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| 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() { 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) { }
}
|
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: 1 2 3 4 5 6 7 8 9 10 11
| 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); } |
|
Programming since 2001 (Technically  ) Uses: BlitzMax, Java, C++ Computers: W7 Desktop and a Triple Booting Netbook.
|
|
|
Galaxy613
JGO n00b  Posts: 25
|
 |
«
Reply #12 on:
2010-04-17 23:17:07 » |
|
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!!** 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
|
public class ToneSong { private int[][] song; private int note = 0; private int pace; 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.
|
Programming since 2001 (Technically  ) Uses: BlitzMax, Java, C++ Computers: W7 Desktop and a Triple Booting Netbook.
|
|
|
bobjob
JGO Ninja    Posts: 646 Medals: 14
David Aaron Muhar
|
 |
«
Reply #13 on:
2010-04-17 23:32:45 » |
|
In that case: for those who want retro style sounds fx, generates tones at runtime, no sound resources are required. example applet: Test Tonesource: Tone Sourcesimple to use you just make static call to the Tone class as follows: 1
| Tone.sound(int tone,int time,double volume); |
queue sound, for music and such: 1
| Tone.soundQueue(int tone,int time,double volume); |
|
|
|
|
Alan_W
JGO Ninja    Posts: 734 Medals: 8
Java tames rock!
|
 |
«
Reply #14 on:
2010-04-18 02:39:55 » |
|
A revision of my EGAImage class that also considers inter colour distance as well as colour frequency when choosing colours. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
|
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;
public class EGAImage extends BufferedImage {
private final static int COLOURS = 64; private final static int PALETTE = 16; private final static int BITS = 6; private final static int PRIMARIES = 8; private static IndexColorModel model = null; private byte[] data; private int[] count = new int[COLOURS]; private int[] parent = new int[COLOURS]; private int[] lower = new int[COLOURS]; private int[] higher = new int[COLOURS];
private boolean[] palette = new boolean[COLOURS]; private boolean[] primary = new boolean[PRIMARIES]; private int colours, primaries; public EGAImage(int width, int height) { super(width, height, BufferedImage.TYPE_BYTE_INDEXED, EGAColourModel()); data = ((DataBufferByte)getRaster().getDataBuffer()).getData(); }
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; }
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; }
public boolean consolidate() { int i, node, lastnode; boolean excessColours = countColors()>PALETTE; if (excessColours) {
for (i=0; i<COLOURS; i++) { parent[i] = lower[i] = higher[i] = -1; palette[i] = false; }
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]; } } } }
node = 0; lastnode = -1; colours = 0;
while (true) { if (lastnode==parent[node]) { 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]) { 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 = parent[node]; } }
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 ^ 0x20]) data[i] ^= 0x20; else if (palette[colour ^ 0x10]) data[i] ^= 0x10; else if (palette[colour ^ 0x28]) data[i] ^= 0x28; else if (palette[colour ^ 0x18]) data[i] ^= 0x18; else if (palette[colour ^ 0x30]) data[i] ^= 0x30; else if (palette[colour ^ 0x38]) data[i] ^= 0x38; else data[i] = (byte)node; } } } return excessColours; }
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); }
}
|
|
Time flies like a bird. Fruit flies like a banana.
|
|
|
Riven
« League of Dukes » JGO Kernel      Posts: 5871 Medals: 255
Hand over your head.
|
 |
«
Reply #15 on:
2010-04-19 15:43:02 » |
|
(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.jarhttp://indiespot.net/files/ega-dithering.jarWebstartable JNLPs: http://indiespot.net/files/ega-no-dithering.jnlphttp://indiespot.net/files/ega-dithering.jnlpSourcecode: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| 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;
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) { for (int i = 0; i < HISTOGRAM.length; i++) { HISTOGRAM[i] = 0; } for (int i = 0; i < pixels.length; i++) { HISTOGRAM[pixels[i]]++; }
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; } }
PALETTE16[i] = maxIndex;
HISTOGRAM[maxIndex] = 0; }
for (int i = 0; i < 64; i++) { 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]; }
for (int i = 0; i < pixels.length; i++) { pixels[i] = FROM_64_TO_16[pixels[i]]; } } } |
|
Hi, appreciate more people! Σ ♥ = ¾ Learn how to award medals... and work your way up the social rankings
|
|
|
moogie
JGO Strike Force    Posts: 775 Medals: 5
Java games rock!
|
 |
«
Reply #16 on:
2010-04-19 16:53:48 » |
|
In my experiments, random dither works well at larger resolutions... at the lower 40x30, the illusion does not seem to be as effective  well for my purposes anyway 
|
|
|
|
|
ShannonSmith
Sr. Member   Posts: 365 Medals: 13
|
 |
«
Reply #17 on:
2010-04-19 16:56:48 » |
|
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.
|
|
|
|
|
Galaxy613
JGO n00b  Posts: 25
|
 |
«
Reply #18 on:
2010-04-19 23:37:17 » |
|
More functions for Markus' EgaImage class/framework: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| 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]; } } } 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..
|
Programming since 2001 (Technically  ) Uses: BlitzMax, Java, C++ Computers: W7 Desktop and a Triple Booting Netbook.
|
|
|
pjt33
JGO Strike Force    Posts: 914 Medals: 17
|
 |
«
Reply #19 on:
2010-04-20 16:09:02 » |
|
Galaxy, any reason you're not using System.arraycopy in copyFrom?
|
|
|
|
|
Galaxy613
JGO n00b  Posts: 25
|
 |
«
Reply #20 on:
2010-04-20 17:41:37 » |
|
Galaxy, any reason you're not using System.arraycopy in copyFrom?
No good reason, just that I didn't know System.arraycopy existed, I'm still a n00b you see.  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.
|
Programming since 2001 (Technically  ) Uses: BlitzMax, Java, C++ Computers: W7 Desktop and a Triple Booting Netbook.
|
|
|
Wyrframe
JGO n00b  Posts: 15
|
 |
«
Reply #21 on:
2010-04-20 22:15:07 » |
|
Galaxy613; try java.util.Arrays; it has several handy functions on arrays, in all appropriate type variations.
|
|
|
|
|
ShannonSmith
Sr. Member   Posts: 365 Medals: 13
|
 |
«
Reply #22 on:
2010-04-24 15:05:10 » |
|
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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| 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){ 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); int rowWidth = (width+1)/2; if (src.length < (rowWidth*height + 16)){ 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){ 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
| 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){ 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; 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| 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 . . . |
|
|
|
|
|
Galaxy613
JGO n00b  Posts: 25
|
 |
«
Reply #23 on:
2010-04-24 18:50:35 » |
|
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.
|
Programming since 2001 (Technically  ) Uses: BlitzMax, Java, C++ Computers: W7 Desktop and a Triple Booting Netbook.
|
|
|
Roquen
JGO Strike Force    Posts: 827 Medals: 25
|
 |
«
Reply #24 on:
2010-04-30 03:33:26 » |
|
Yet another full palette intializer (linear form): 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 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. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| public static final long colorUsage(int[] data, int len) { long c = 0; int x = 0; int y = 0; int d,r; 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); } if (((x|y) & 0xF0F0F) != 0) return 0; return c; }
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: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public static final boolean isValidRGB(int rgb) { int x = (rgb ^ (rgb >> 4)); int y = (rgb ^ (rgb >> 2)); return ((x|y) & 0xF0F0F) == 0; }
public static final int RGBToIndex(int rgb) { int r = rgb & 0x030303; r |= (r >> 6); r |= (r >> 12); return r & 0x3f; }
public static final int indexToRGB(int r) { r = (r | (r << 6) | (r << 12)) & 0x030303; r |= r << 2; r |= r << 4; return r; }
|
|
|
|
|
|
Markus_Persson
JGO Kernel      Posts: 2092 Medals: 10
Mojang Specifications
|
 |
«
Reply #25 on:
2010-04-30 05:59:21 » |
|
Neato!  That's some creepy compressed obfuscated magic, I love it! =D
|
|
|
|
|