I am currently working on a 2D-simulator game that takes place in a Perlin noise-generated terrain that is shown on a 41x23 grid. The player (as of the moment, at the center but not yet given an overlaying icon) can move using the arrow keys, but doing so will keep the player static but move the map accordingly. However, when I move, the JFrame lags like hell. Some JLabel instances change their ImageIcons slower than others, creating huge latency and un-"playability". I have tried replacing the inefficient function update with four functions that "efficiently" move the player faster - but the lag or delay remains. I have also reformatted and refactored the function, to no avail. So, I am stuck.
For more info, I am using 32x32 icons that represent the structures and the domain, and the JFrame is 1280x720 in size. I am confident that this is not due to hardware, as the program runs with other memory- or core- consuming programs. Is there any way to solve the lag or delay?
Main Class
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.awt.image.RescaleOp;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.GridBagLayout;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
public class Main {
private JFrame frame;
public static ImageIcon water = new ImageIcon(new ImageIcon(Main.class.getResource("/textures/terrain/water.png")).getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT));
public static ImageIcon sand = new ImageIcon(new ImageIcon(Main.class.getResource("/textures/terrain/sand.png")).getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT));
public static ImageIcon grass = new ImageIcon(new ImageIcon(Main.class.getResource("/textures/terrain/grass.png")).getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT));
public static ImageIcon stone = new ImageIcon(new ImageIcon(Main.class.getResource("/textures/terrain/stone.png")).getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT));
public static ImageIcon ice = new ImageIcon(new ImageIcon(Main.class.getResource("/textures/terrain/ice.png")).getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT));
public static ImageIcon oak = new ImageIcon(new ImageIcon(Main.class.getResource("/textures/structure/oak.png")).getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT));
public static ImageIcon nullstructure = new ImageIcon(new ImageIcon(Main.class.getResource("/textures/structure/nullstructure.png")).getImage().getScaledInstance(32, 32, Image.SCALE_DEFAULT));
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main window = new Main();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
* #throws InterruptedException
*/
public Main() throws InterruptedException {
initialize();
}
/**
* Initialize the contents of the frame.
* #throws InterruptedException
*/
private void initialize() throws InterruptedException {
frame = new JFrame();
frame.setResizable(false);
frame.setBounds(0, 0, 1280, 720);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
Coordinate playerPos = new Coordinate(0,0);
JLabel[][] terrainArray = new JLabel[41][23];
JLabel[][] structureArray = new JLabel[41][23];
JLayeredPane layeredPane = new JLayeredPane();
layeredPane.setBounds(0, 0, 1280, 720);
frame.getContentPane().add(layeredPane);
layeredPane.setLayout(null);
JPanel terrainGrid = new JPanel();
terrainGrid.setBounds(0, 0, 1280, 720);
layeredPane.add(terrainGrid);
GridBagLayout gbl_terrainGrid = new GridBagLayout();
gbl_terrainGrid.columnWidths = new int[]{0};
gbl_terrainGrid.rowHeights = new int[]{0};
gbl_terrainGrid.columnWeights = new double[]{Double.MIN_VALUE};
gbl_terrainGrid.rowWeights = new double[]{Double.MIN_VALUE};
terrainGrid.setLayout(gbl_terrainGrid);
JPanel structureGrid = new JPanel();
layeredPane.setLayer(structureGrid, Integer.valueOf(1));
structureGrid.setBounds(0, 0, 1280, 720);
structureGrid.setBackground(new Color(0,0,0,0));
structureGrid.setOpaque(false);
layeredPane.add(structureGrid);
GridBagLayout gbl_structureGrid = new GridBagLayout();
gbl_structureGrid.columnWidths = new int[]{0};
gbl_structureGrid.rowHeights = new int[]{0};
gbl_structureGrid.columnWeights = new double[]{Double.MIN_VALUE};
gbl_structureGrid.rowWeights = new double[]{Double.MIN_VALUE};
structureGrid.setLayout(gbl_structureGrid);
Coordinate[][] map = new Coordinate[Coordinate.MAP_SIZE][Coordinate.MAP_SIZE];
for(int i = 0; i < Coordinate.MAP_SIZE; i++) {
for(int j = 0; j < Coordinate.MAP_SIZE; j++) {
map[i][j] = new Coordinate(i - ((Coordinate.MAP_SIZE - 1)/2) , j - ((Coordinate.MAP_SIZE - 1)/2));
}
}
for(int i = 0; i < 41; i++) {
for(int j = 0; j < 23; j++) {
terrainArray[i][j] = new JLabel("");
structureArray[i][j] = new JLabel("");
structureArray[i][j].setIcon(Main.nullstructure);
terrainArray[i][j].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + i, playerPos.getZ() - 11 + j)).returnTerrainIcon());
structureArray[i][j].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + i, playerPos.getZ() - 11 + j)).returnStructureIcon());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = i; gbc.gridy = j;
terrainGrid.add(terrainArray[i][j], gbc);
structureGrid.add(structureArray[i][j],gbc);
}
}
frame.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
switch(e.getKeyCode()) {
case KeyEvent.VK_UP:
playerPos.setZ(playerPos.getZ() - 1);
moveUP(terrainArray, structureArray, map, playerPos);
break;
case KeyEvent.VK_DOWN:
playerPos.setZ(playerPos.getZ() + 1);
moveDOWN(terrainArray, structureArray, map, playerPos);
break;
case KeyEvent.VK_RIGHT:
playerPos.setX(playerPos.getX() + 1);
moveRIGHT(terrainArray, structureArray, map, playerPos);
break;
case KeyEvent.VK_LEFT:
playerPos.setX(playerPos.getX() - 1);
moveLEFT(terrainArray, structureArray, map, playerPos);
break;
}
}
});
}
public void moveUP(JLabel[][] terrainArray, JLabel[][] structureArray, Coordinate[][] map, Coordinate playerPos) {
for(int x = 0; x < 41; x++) {
for(int z = 22; z > 0; z--) { //23 - 1
terrainArray[x][z].setIcon(terrainArray[x][z-1].getIcon());
structureArray[x][z].setIcon(structureArray[x][z-1].getIcon());
}
terrainArray[x][0].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + x, playerPos.getZ() - 11)).returnTerrainIcon());
structureArray[x][0].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + x, playerPos.getZ() - 11)).returnStructureIcon());
}
}
public void moveDOWN(JLabel[][] terrainArray, JLabel[][] structureArray, Coordinate[][] map, Coordinate playerPos) {
for(int x = 0; x < 41; x++) {
for(int z = 0; z < 22; z++) { //23 - 1
terrainArray[x][z].setIcon(terrainArray[x][z+1].getIcon());
structureArray[x][z].setIcon(structureArray[x][z+1].getIcon());
}
terrainArray[x][22].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + x, playerPos.getZ() + 11)).returnTerrainIcon());
structureArray[x][22].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + x, playerPos.getZ() + 11)).returnStructureIcon());
}
}
public void moveLEFT(JLabel[][] terrainArray, JLabel[][] structureArray, Coordinate[][] map, Coordinate playerPos) {
for(int z = 0; z < 23; z++) {
for(int x = 40; x > 0; x--) {
terrainArray[x][z].setIcon(terrainArray[x-1][z].getIcon());
structureArray[x][z].setIcon(structureArray[x-1][z].getIcon());
}
terrainArray[0][z].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20, playerPos.getZ() - 11 + z)).returnTerrainIcon());
structureArray[0][z].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20, playerPos.getZ() - 11 + z)).returnStructureIcon());
}
}
public void moveRIGHT(JLabel[][] terrainArray, JLabel[][] structureArray, Coordinate[][] map, Coordinate playerPos) {
for(int z = 0; z < 23; z++) {
for(int x = 0; x < 40; x++) {
terrainArray[x][z].setIcon(terrainArray[x+1][z].getIcon());
structureArray[x][z].setIcon(structureArray[x+1][z].getIcon());
}
terrainArray[40][z].setIcon(findEntry(map, new Coordinate(playerPos.getX() + 20, playerPos.getZ() - 11 + z)).returnTerrainIcon());
structureArray[40][z].setIcon(findEntry(map, new Coordinate(playerPos.getX() + 20, playerPos.getZ() - 11 + z)).returnStructureIcon());
}
}
public static ImageIcon brightenImage(ImageIcon input, float brightness, float offset) {
BufferedImage bI = new BufferedImage(input.getImage().getWidth(null), input.getImage().getHeight(null), BufferedImage.TYPE_INT_RGB);
Graphics2D bIgr = bI.createGraphics();
bIgr.drawImage(input.getImage(), 0, 0, null);
bIgr.dispose();
BufferedImage bO = new BufferedImage(input.getImage().getWidth(null), input.getImage().getHeight(null), BufferedImage.TYPE_INT_RGB);
RescaleOp rop = new RescaleOp(brightness, offset, null);
bO = rop.filter(bI, null);
ImageIcon output = new ImageIcon(bO);
return output;
}
// public void update(JLabel[][] terrainArray, JLabel[][] structureArray, Coordinate[][] map, Coordinate playerPos) {
// for(int i = 0; i < 41; i++) {
// for(int j = 0; j < 23; j++) {
// terrainArray[i][j].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + i, playerPos.getZ() - 11 + j)).returnTerrainIcon());
// structureArray[i][j].setIcon(findEntry(map, new Coordinate(playerPos.getX() - 20 + i, playerPos.getZ() - 11 + j)).returnStructureIcon());
// }
// }
// }
public Coordinate findEntry(Coordinate[][] map, Coordinate pos) {
Coordinate entry = null;
for(int x = 0; x < Coordinate.MAP_SIZE; x++) {
for(int z = 0; z < Coordinate.MAP_SIZE; z++) {
if(pos.getX() == map[x][z].getX() && pos.getZ() == map[x][z].getZ()) {
entry = map[x][z];
}
}
}
return entry;
}
}
Coordinate Class
import javax.swing.ImageIcon;
public class Coordinate {
private int x, z;
private int alt;
public static final int MAP_SIZE = 199;
private ImageIcon str = Main.nullstructure;
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getZ() {
return z;
}
public void setZ(int z) {
this.z = z;
}
public int getAlt() {
return alt;
}
public void setAlt(int alt) {
this.alt = alt;
}
public Coordinate(int x0, int z0) {
this.x = x0;
this.z = z0;
int a = (int) Noise.mapTo(-1 * Math.sqrt(0.5), Math.sqrt(0.5), 0, 100, Noise.noise(x*0.1, z*0.1));
int b = (int) Noise.mapTo(-1 * Math.sqrt(0.5), Math.sqrt(0.5), 0, 100, Noise.noise(x*0.05, z*0.05));
int c = (int) Noise.mapTo(-1 * Math.sqrt(0.5), Math.sqrt(0.5), 0, 100, Noise.noise(x*0.01, z*0.01));
this.alt = (int) ((0.55*c) + (0.25*b) + (0.20*a));
if(Math.random() < 0.05 && alt >= 52 && alt < 70) {
str = Main.oak;
}
}
public ImageIcon returnTerrainIcon() {
if(alt >= 0 && alt < 50) {
return Main.water;
}
else if(alt >= 50 && alt < 52) {
return Main.brightenImage(Main.sand, (float) ((float) 0.95 + ((alt - 50) * 0.025)), (float) 0.36);
}
else if(alt >= 52 && alt < 70) {
return Main.brightenImage(Main.grass, (float) ((float) 0.85 + ((alt - 52) * 0.025)), (float) 0.36);
}
else if(alt >= 80 && alt < 90) {
return Main.brightenImage(Main.stone, (float) ((float) 0.65 + ((alt - 70) * 0.05)), (float) 0.36);
}
else {
return Main.ice;
}
}
public ImageIcon returnStructureIcon() {
return str;
}
}
Perlin (Not original)
import java.util.Random;
//<pre>
// Copyright 2001 Ken Perlin
// Courtesy of https://mrl.cs.nyu.edu/~perlin/experiments/packing/render/Noise.java
/**
Computes Perlin Noise for one, two, and three dimensions.<p>
The result is a continuous function that interpolates a smooth path
along a series random points. The function is consitent, so given
the same parameters, it will always return the same value.
#see ImprovedNoise
*/
public final class Noise {
/**
Initialization seed used to start the random number generator.
*/
static Random randseed = new Random();
public static int seed = (int) Math.floor(randseed.nextInt());
private static final int P = 8;
private static final int B = 1 << P;
private static final int M = B - 1;
private static final int NP = 8;
private static final int N = 1 << NP;
private static int p[] = new int[B + B + 2];
private static double g2[][] = new double[B + B + 2][2];
private static double g1[] = new double[B + B + 2];
private static double[][] points = new double[32][3];
static {
init();
}
private static double lerp(double t, double a, double b) {
return a + t * (b - a);
}
private static double s_curve(double t) {
return t * t * (3 - t - t);
}
/**
Computes noise function for one dimension at x.
#param x 1 dimensional parameter
#return the noise value at x
*/
public static double noise(double x) {
int bx0, bx1;
double rx0, rx1, sx, t, u, v;
t = x + N;
bx0 = ((int) t) & M;
bx1 = (bx0 + 1) & M;
rx0 = t - (int) t;
rx1 = rx0 - 1;
sx = s_curve(rx0);
u = rx0 * g1[p[bx0]];
v = rx1 * g1[p[bx1]];
return lerp(sx, u, v);
}
/**
Computes noise function for two dimensions at the point (x,y).
#param x x dimension parameter
#param y y dimension parameter
#return the value of noise at the point (x,y)
*/
public static double noise(double x, double y) {
int bx0, bx1, by0, by1, b00, b10, b01, b11;
double rx0, rx1, ry0, ry1, sx, sy, a, b, t, u, v, q[];
int i, j;
t = x + N;
bx0 = ((int) t) & M;
bx1 = (bx0 + 1) & M;
rx0 = t - (int) t;
rx1 = rx0 - 1;
t = y + N;
by0 = ((int) t) & M;
by1 = (by0 + 1) & M;
ry0 = t - (int) t;
ry1 = ry0 - 1;
i = p[bx0];
j = p[bx1];
b00 = p[i + by0];
b10 = p[j + by0];
b01 = p[i + by1];
b11 = p[j + by1];
sx = s_curve(rx0);
sy = s_curve(ry0);
q = g2[b00];
u = rx0 * q[0] + ry0 * q[1];
q = g2[b10];
v = rx1 * q[0] + ry0 * q[1];
a = lerp(sx, u, v);
q = g2[b01];
u = rx0 * q[0] + ry1 * q[1];
q = g2[b11];
v = rx1 * q[0] + ry1 * q[1];
b = lerp(sx, u, v);
return lerp(sy, a, b);
}
/**
Computes noise function for three dimensions at the point (x,y,z).
#param x x dimension parameter
#param y y dimension parameter
#param z z dimension parameter
#return the noise value at the point (x, y, z)
*/
static public double noise(double x, double y, double z) {
int bx, by, bz, b0, b1, b00, b10, b01, b11;
double rx0, rx1, ry0, ry1, rz, sx, sy, sz, a, b, c, d, u, v, q[];
bx = (int) Math.IEEEremainder(Math.floor(x), B);
if (bx < 0)
bx += B;
rx0 = x - Math.floor(x);
rx1 = rx0 - 1;
by = (int) Math.IEEEremainder(Math.floor(y), B);
if (by < 0)
by += B;
ry0 = y - Math.floor(y);
ry1 = ry0 - 1;
bz = (int) Math.IEEEremainder(Math.floor(z), B);
if (bz < 0)
bz += B;
rz = z - Math.floor(z);
//if (bx < 0 || bx >= B + B + 2)
//System.out.println(bx);
b0 = p[bx];
bx++;
b1 = p[bx];
b00 = p[b0 + by];
b10 = p[b1 + by];
by++;
b01 = p[b0 + by];
b11 = p[b1 + by];
sx = s_curve(rx0);
sy = s_curve(ry0);
sz = s_curve(rz);
q = G(b00 + bz);
u = rx0 * q[0] + ry0 * q[1] + rz * q[2];
q = G(b10 + bz);
v = rx1 * q[0] + ry0 * q[1] + rz * q[2];
a = lerp(sx, u, v);
q = G(b01 + bz);
u = rx0 * q[0] + ry1 * q[1] + rz * q[2];
q = G(b11 + bz);
v = rx1 * q[0] + ry1 * q[1] + rz * q[2];
b = lerp(sx, u, v);
c = lerp(sy, a, b);
bz++;
rz--;
q = G(b00 + bz);
u = rx0 * q[0] + ry0 * q[1] + rz * q[2];
q = G(b10 + bz);
v = rx1 * q[0] + ry0 * q[1] + rz * q[2];
a = lerp(sx, u, v);
q = G(b01 + bz);
u = rx0 * q[0] + ry1 * q[1] + rz * q[2];
q = G(b11 + bz);
v = rx1 * q[0] + ry1 * q[1] + rz * q[2];
b = lerp(sx, u, v);
d = lerp(sy, a, b);
return lerp(sz, c, d);
}
private static double[] G(int i) {
return points[i % 32];
}
private static void init() {
int i, j, k;
double u, v, w, U, V, W, Hi, Lo;
java.util.Random r = new java.util.Random(seed);
for (i = 0; i < B; i++) {
p[i] = i;
g1[i] = 2 * r.nextDouble() - 1;
do {
u = 2 * r.nextDouble() - 1;
v = 2 * r.nextDouble() - 1;
} while (u * u + v * v > 1 || Math.abs(u) > 2.5 * Math.abs(v) || Math.abs(v) > 2.5 * Math.abs(u) || Math.abs(Math.abs(u) - Math.abs(v)) < .4);
g2[i][0] = u;
g2[i][1] = v;
normalize2(g2[i]);
do {
u = 2 * r.nextDouble() - 1;
v = 2 * r.nextDouble() - 1;
w = 2 * r.nextDouble() - 1;
U = Math.abs(u);
V = Math.abs(v);
W = Math.abs(w);
Lo = Math.min(U, Math.min(V, W));
Hi = Math.max(U, Math.max(V, W));
} while (u * u + v * v + w * w > 1 || Hi > 4 * Lo || Math.min(Math.abs(U - V), Math.min(Math.abs(U - W), Math.abs(V - W))) < .2);
}
while (--i > 0) {
k = p[i];
j = (int) (r.nextLong() & M);
p[i] = p[j];
p[j] = k;
}
for (i = 0; i < B + 2; i++) {
p[B + i] = p[i];
g1[B + i] = g1[i];
for (j = 0; j < 2; j++) {
g2[B + i][j] = g2[i][j];
}
}
points[3][0] = points[3][1] = points[3][2] = Math.sqrt(1. / 3);
double r2 = Math.sqrt(1. / 2);
double s = Math.sqrt(2 + r2 + r2);
for (i = 0; i < 3; i++)
for (j = 0; j < 3; j++)
points[i][j] = (i == j ? 1 + r2 + r2 : r2) / s;
for (i = 0; i <= 1; i++)
for (j = 0; j <= 1; j++)
for (k = 0; k <= 1; k++) {
int n = i + j * 2 + k * 4;
if (n > 0)
for (int m = 0; m < 4; m++) {
points[4 * n + m][0] = (i == 0 ? 1 : -1) * points[m][0];
points[4 * n + m][1] = (j == 0 ? 1 : -1) * points[m][1];
points[4 * n + m][2] = (k == 0 ? 1 : -1) * points[m][2];
}
}
}
private static void normalize2(double v[]) {
double s;
s = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
v[0] = v[0] / s;
v[1] = v[1] / s;
}
public static double mapTo(double a1, double a2, double b1, double b2, double x) {
return b1 + ((x-a1)*(b2-b1))/(a2-a1);
}
}
The icons and resources to be used are in this Github code (repository, I guess, I am new to Github):
https://github.com/rubiksRepository/Perlin.git
I would appreciate any help given. Thanks!
So far, Raildex's suggestion is working. The compilation of the map into a single buffered image has reduced the lag tremendously and made the game "playable". For additions in the code, I have made a texture class that supports the textures used; and a MapField whose object has fields that have the compiled BufferImage.
import java.awt.image.BufferedImage;
public class MapField {
private BufferedImage terrain;
private BufferedImage structure;
public MapField(Coordinate[][] map) {
BufferedImage[] colsT = new BufferedImage[Coordinate.MAP_SIZE];
BufferedImage[] colsS = new BufferedImage[Coordinate.MAP_SIZE];
for(int z = 0; z < Coordinate.MAP_SIZE; z++) {
colsT[z] = Texture.mergeTeU(map[z]);
colsS[z] = Texture.mergeStU(map[z]);
}
terrain = Texture.mergeH(colsT);
structure = Texture.mergeH(colsS);
}
public BufferedImage getTerrain() {
return terrain;
}
public void setTerrain(BufferedImage terrain) {
this.terrain = terrain;
}
public BufferedImage getStructure() {
return structure;
}
public void setStructure(BufferedImage structure) {
this.structure = structure;
}
}
Also in the Texture class, there are methods that can be used to merge rows and columns of BufferedImages, using a Graphics2D drawing process:
public static BufferedImage toBI(ImageIcon input) {
BufferedImage output = new BufferedImage(input.getImage().getWidth(null), input.getImage().getHeight(null), BufferedImage.TYPE_INT_ARGB);
Graphics2D outputGr = output.createGraphics();
outputGr.drawImage(input.getImage(), 0, 0, null);
outputGr.dispose();
return output;
}
public static BufferedImage mergeH(BufferedImage[] x) {
int i = 0;
BufferedImage rowOutput = new BufferedImage(x[0].getWidth() * x.length, x[0].getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D rowOutputG = rowOutput.createGraphics();
for(BufferedImage xBI : x) {
rowOutputG.drawImage(xBI, i * x[0].getWidth(), 0, null);
i++;
}
rowOutputG.dispose();
return rowOutput;
}
public static BufferedImage mergeTeH(Coordinate[] x) {
BufferedImage[] xIcons = new BufferedImage[x.length];
for(int i = 0; i < x.length; i++) {
xIcons[i] = Texture.toBI(x[i].returnTerrainIcon());
}
return mergeH(xIcons);
}
public static BufferedImage mergeStH(Coordinate[] x) {
BufferedImage[] xIcons = new BufferedImage[x.length];
for(int i = 0; i < x.length; i++) {
xIcons[i] = Texture.toBI(x[i].returnStructureIcon());
}
return mergeH(xIcons);
}
public static BufferedImage mergeU(BufferedImage[] x) {
int i = 0;
BufferedImage colOutput = new BufferedImage(x[0].getWidth(), x[0].getHeight() * x.length, BufferedImage.TYPE_INT_ARGB);
Graphics2D colOutputG = colOutput.createGraphics();
for(BufferedImage xBI : x) {
colOutputG.drawImage(xBI, 0, i * x[0].getHeight(), null);
i++;
}
colOutputG.dispose();
return colOutput;
}
public static BufferedImage mergeTeU(Coordinate[] x) {
BufferedImage[] xIcons = new BufferedImage[x.length];
for(int i = 0; i < x.length; i++) {
xIcons[i] = Texture.toBI(x[i].returnTerrainIcon());
}
return mergeU(xIcons);
}
public static BufferedImage mergeStU(Coordinate[] x) {
BufferedImage[] xIcons = new BufferedImage[x.length];
for(int i = 0; i < x.length; i++) {
xIcons[i] = Texture.toBI(x[i].returnStructureIcon());
}
return mergeU(xIcons);
}
So the bottomline is: for scroll-based maps, you can compile the textures corresponding to the map to improve playability (FPS or lag reduction). For interaction with the game, editing the BufferedImage at a pixel range via turning it to transparency or changing textures can help, at least for me. Not really great in answering the general gist since I am addressing my case only (trying to emulate this solution on others though) and the scroll-base map is on a case-to-case basis, but I hope my solution helps.
I am trying to convert RGB values of an image and get the HSV values of it. I am trying to do this without using Color.RGBtoHSB because I don't like the float values I want the numbers to be in the range of 0-255. When I run this program my conversion algorithm prints out nothing even though I ask it to print out values.
public void splitChannels() {
Mat firstImage = Imgcodecs.imread("darkGreen.jpg");
Imgproc.cvtColor(firstImage, firstImage, Imgproc.COLOR_BGR2RGB);
int width = 20;
int height = 20;
Rect roi = new Rect(100,100, width, height);
Mat smallImg = new Mat(firstImage, roi);
Imgproc.cvtColor(smallImg,smallImg,Imgproc.COLOR_BGR2RGB);
// 3 channels in smallImg
int channels = smallImg.channels();
int totalBytes = (int)(smallImg.total() * smallImg.channels());
byte buff[] = new byte[totalBytes];
smallImg.get(0, 0, buff);
for (int i=0; i< height; i++) {
// stride is the number of bytes in a row of smallImg
int stride = channels * width;
for (int j=0; j<stride; j+=channels) {
int r = buff[(i * stride) + j];
int g = buff[(i * stride) + j + 1];
int b = buff[(i * stride) + j + 2];
RGBtoHSV(r, g, b);
}
}
}
private int[] RGBtoHSV(int r, int g, int b){
int computedH = 0;
int computedS = 0;
int computedV = 0;
int[] HSVarr = new int[3];
HSVarr[0] = computedH;
HSVarr[1] = computedS;
HSVarr[2] = computedV;
if(r< 0 || g< 0 || b< 0 || r> 255 || g>255 || b> 255){
System.err.println("RGB values must be in range 0 to 255");
}
r=r/255; g=g/255; b=b/255;
int minRGB = Math.min(r, Math.min(g, b));
int maxRGB = Math.max(r, Math.min(g, b));
// Black-gray-white
if(minRGB==maxRGB){
computedV = minRGB;
return HSVarr;
}
int d = (r==minRGB) ? g-b : ((b==minRGB) ? r-g : b-r);
int h = (r==minRGB) ? 3 : ((b==minRGB) ? 1 : 5);
computedH = 60*(h - d/(maxRGB - minRGB));
computedS = (maxRGB = minRGB)/maxRGB;
computedV = maxRGB;
System.out.println("H: " + computedH + " V: "+ computedS +" S: " + computedV);
return HSVarr;
}
I am trying to convert RGB values of an image and get the HSV values of it. I am trying to do this without using Color.RGBtoHSB because I don't like the float values.
Create a wrapper method for the Color.RGBtoHSB(...) method to convert the float values to the appropriate int values.
Something like:
import java.awt.*;
public class Main
{
public static void main(String[] args) throws Exception
{
int[] hsb = RGBtoHSB(0, 0, 255);
for (int value: hsb)
System.out.println( value );
}
public static int[] RGBtoHSB(int red, int green, int blue)
{
float[] hsbFloat = Color.RGBtoHSB(red, green, blue, null);
int[] hsbInt = new int[3];
hsbInt[0] = Math.round( hsbFloat[0] * 360 );
hsbInt[1] = Math.round( hsbFloat[1] * 100 );
hsbInt[2] = Math.round( hsbFloat[2] * 100 );
return hsbInt;
}
}
The equation for generating a Mandelbrot fractal is Zn+1 = Zn^2+C. The issue is that, in a computer program, C is used for zoom/resolution and location on screen. My question is, how can I make it so that I can get a fractal like this:
Wolfram
(equation f(z) = sin(z/c), z0 = c )
My code (from Rosetta Code):
public class MandelbrotSet extends JFrame {
private static final long serialVersionUID = 5513426498262284949L;
private final int MAX_ITER = 570;
private final double ZOOM = 150;
private BufferedImage image;
private double zx, zy, cX, cY, tmp;
public MandelbrotSet() {
super("Mandelbrot Set");
setBounds(100, 100, 800, 600);
setResizable(false);
setDefaultCloseOperation(EXIT_ON_CLOSE);
image = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
for (int y = 0; y < getHeight(); y++) {
for (int x = 0; x < getWidth(); x++) {
zx = zy = 0;
cX = (x - 400) / ZOOM;
cY = (y - 300) / ZOOM;
int iter = MAX_ITER;
while (zx * zx + zy * zy < 4 && iter > 0) {
tmp = zx * zx - zy * zy + cX;
zy = 2.0 * zx * zy + cY;
zx = tmp;
iter--;
}
image.setRGB(x, y, iter | (iter << 8));
}
}
}
#Override
public void paint(Graphics g) {
g.drawImage(image, 0, 0, this);
}
public static void main(String[] args) {
new MandelbrotSet().setVisible(true);;
}
}
By trigonometric theorems
sin(A+i*B)=sin(A)*cos(i*B)+ cos(A)*sin(i*B)
=sin(A)*cosh(B )+i*cos(A)*sinh(B )
and for the quotient using z=x+i*y and c=a+i*b
(x+i*y)/(a+i*b)=(x+i*y)*(a-i*b)/(a*a+b*b)
so that for the sine expression above
A = (a*x+b*y)/(a*a+b*b)
B = (a*y-b*x)/(a*a+b*b)
In javascript a small script to generate this fractal can look like this:
function cosh(x) { return 0.5*(Math.exp(x)+Math.exp(-x)); }
function sinh(x) { return 0.5*(Math.exp(x)-Math.exp(-x)); }
function rgb(r,g,b) { return "rgb("+r+","+g+","+b+")"; }
var colors = new Array(24);
for(var k=0; k<8; k++) {
colors[ k] = rgb(k*64,(7-k)*64,(7-k)*64);
colors[ 8+k] = rgb((7-k)*64,k*64,(7-k)*64);
colors[16+k] = rgb((7-k)*64,(7-k)*64,k*64);
}
var cvs = document.getElementById('sine-fractal');
var ctx = cvs.getContext('2d');
var cx = 0.0, cy = 0.0;
var dx = 1.0;
var tiles = 100;
var scale = Math.min(cvs.width, cvs.height) / tiles;
ctx.scale(scale, scale);
function localx(i) { return cx-dx + 2*i*dx/tiles; }
function localy(j) { return cy-dx + 2*j*dx/tiles; }
for (var i = 0; i < tiles; i++) {
var a = localx(i);
for (var j = 0; j < tiles; j++) {
var b = localy(j);
var r2 = a*a + b*b;
var x = a, y = b;
var rounds = 0;
var max = 500;
while (x * x + y * y < 4 && rounds < max) {
var u = (a*x + b*y) / r2, v = (a*y - b*x) / r2;
x = Math.sin(u) * cosh(v);
y = Math.cos(u) * sinh(v);
rounds++;
}
ctx.fillStyle = colors[rounds % 24];
ctx.fillRect(i, j, 1, 1);
}
}
<canvas id='sine-fractal' width=200 height=200></canvas>
in the example which is given in the book "Computer Graphics for Java Programmers, Second Edition"
there is transformation with view vector
private void shiftToOrigin()
{ float xwC = 0.5F * (xMin + xMax),
ywC = 0.5F * (yMin + yMax),
zwC = 0.5F * (zMin + zMax);
int n = w.size();
for (int i=1; i<n; i++)
if (w.elementAt(i) != null)
{ ((Point3D)w.elementAt(i)).x -= xwC;
((Point3D)w.elementAt(i)).y -= ywC;
((Point3D)w.elementAt(i)).z -= zwC;
}
float dx = xMax - xMin, dy = yMax - yMin, dz = zMax - zMin;
rhoMin = 0.6F * (float) Math.sqrt(dx * dx + dy * dy + dz * dz);
rhoMax = 1000 * rhoMin;
rho = 3 * rhoMin;
}
private void initPersp()
{
float costh = (float)Math.cos(theta),
sinth = (float)Math.sin(theta),
cosph = (float)Math.cos(phi),
sinph = (float)Math.sin(phi);
v11 = -sinth; v12 = -cosph * costh; v13 = sinph * costh;
v21 = costh; v22 = -cosph * sinth; v23 = sinph * sinth;
v32 = sinph; v33 = cosph;
v43 = -rho;
}
float eyeAndScreen(Dimension dim)
// Called in paint method of Canvas class
{ initPersp();
int n = w.size();
e = new Point3D[n];
vScr = new Point2D[n];
float xScrMin=1e30F, xScrMax=-1e30F,
yScrMin=1e30F, yScrMax=-1e30F;
for (int i=1; i<n; i++)
{
Point3D P = (Point3D)(w.elementAt(i));
if (P == null)
{ e[i] = null; vScr[i] = null;
}
else
{ float x = v11 * P.x + v21 * P.y;
float y = v12 * P.x + v22 * P.y + v32 * P.z;
float z = v13 * P.x + v23 * P.y + v33 * P.z + v43;
Point3D Pe = e[i] = new Point3D(x, y, z);
float xScr = -Pe.x/Pe.z, yScr = -Pe.y/Pe.z;
vScr[i] = new Point2D(xScr, yScr);
if (xScr < xScrMin) xScrMin = xScr;
if (xScr > xScrMax) xScrMax = xScr;
if (yScr < yScrMin) yScrMin = yScr;
if (yScr > yScrMax) yScrMax = yScr;
}
}
float rangeX = xScrMax - xScrMin, rangeY = yScrMax - yScrMin;
d = 0.95F * Math.min(dim.width/rangeX, dim.height/rangeY); //d burada
imgCenter = new Point2D(d * (xScrMin + xScrMax)/2,
d * (yScrMin + yScrMax)/2);
for (int i=1; i<n; i++)
{
if (vScr[i] != null){vScr[i].x *= d; vScr[i].y *= d;}
}
return d * Math.max(rangeX, rangeY);
// Maximum screen-coordinate range used in CvHLines for HP-GL
}
here float xScr = -Pe.x/Pe.z, yScr = -Pe.y/Pe.z; when we divide x and y with z that give as a perspective view if we don't divide it with z the view will be parallel(orthogonal)
this is OK but if we want to this parallel view coordinates with hidden line algorithm in same book it calculates lines wrongly. I couldn't find where problem is. What can cause this problem?
here hidden line algorithm:
private void lineSegment(Graphics g, Point3D Pe, Point3D Qe,
Point2D PScr, Point2D QScr, int iP, int iQ, int iStart)
{
double u1 = QScr.x - PScr.x; //t
double u2 = QScr.y - PScr.y; //t
double minPQx = Math.min(PScr.x, QScr.x);//t
double maxPQx = Math.max(PScr.x, QScr.x);//t
double minPQy = Math.min(PScr.y, QScr.y);//t
double maxPQy = Math.max(PScr.y, QScr.y);//t
double zP = Pe.z; //t
double zQ = Qe.z; //t
double minPQz = Math.min(zP, zQ);//t
Point3D[] e = obj.getE();//e eye
Point2D[] vScr = obj.getVScr(); //vscr screen
for (int i=iStart; i<nTria; i++)//t
{
Tria t = tr[i];
int iA = t.iA, iB = t.iB, iC = t.iC;
Point2D AScr = vScr[iA], BScr = vScr[iB], CScr = vScr[iC];
// 1. Minimax test for x and y screen coordinates: //t
if (maxPQx <= AScr.x && maxPQx <= BScr.x && maxPQx <= CScr.x
|| minPQx >= AScr.x && minPQx >= BScr.x && minPQx >= CScr.x
|| maxPQy <= AScr.y && maxPQy <= BScr.y && maxPQy <= CScr.y
|| minPQy >= AScr.y && minPQy >= BScr.y && minPQy >= CScr.y)
continue;
// 2. Test if PQ is an edge of ABC: //t
if ((iP == iA || iP == iB || iP == iC) &&
(iQ == iA || iQ == iB || iQ == iC))
continue;
// 3. Test if PQ is clearly nearer than ABC://t
Point3D Ae = e[iA], Be = e[iB], Ce = e[iC];
double zA = Ae.z, zB = Be.z, zC = Ce.z;
if (minPQz >= zA && minPQz >= zB && minPQz >= zC)
continue;
// 4. Do P and Q (in 2D) lie in a half plane defined
// by line AB, on the side other than that of C?
// Similar for the edges BC and CA.
double eps = 0.1; // Relative to numbers of pixels //t
if (Tools2D.area2(AScr, BScr, PScr) < eps &&
Tools2D.area2(AScr, BScr, QScr) < eps ||
Tools2D.area2(BScr, CScr, PScr) < eps &&
Tools2D.area2(BScr, CScr, QScr) < eps ||
Tools2D.area2(CScr, AScr, PScr) < eps &&
Tools2D.area2(CScr, AScr, QScr) < eps)
continue;
// 5. Test (2D) if A, B and C lie on the same side
// of the infinite line through P and Q://t
double PQA = Tools2D.area2(PScr, QScr, AScr);
double PQB = Tools2D.area2(PScr, QScr, BScr);
double PQC = Tools2D.area2(PScr, QScr, CScr);
if (PQA < +eps && PQB < +eps && PQC < +eps ||
PQA > -eps && PQB > -eps && PQC > -eps)
continue;
// 6. Test if neither P nor Q lies behind the
// infinite plane through A, B and C://t
int iPol = refPol[i];
Polygon3D pol = (Polygon3D)polyList.elementAt(iPol);
double a = pol.getA(), b = pol.getB(), c = pol.getC(),
h = pol.getH(), eps1 = 1e-5 * Math.abs(h),
hP = a * Pe.x + b * Pe.y + c * Pe.z,
hQ = a * Qe.x + b * Qe.y + c * Qe.z;
if (hP > h - eps1 && hQ > h - eps1)
continue;
// 7. Test if both P and Q behind triangle ABC://t
boolean PInside =
Tools2D.insideTriangle(AScr, BScr, CScr, PScr);
boolean QInside =
Tools2D.insideTriangle(AScr, BScr, CScr, QScr);
if (PInside && QInside)
return;
// 8. If P nearer than ABC and inside, PQ visible;//t
// the same for Q:
double h1 = h + eps1;
boolean PNear = hP > h1, QNear = hQ > h1;
if (PNear && PInside || QNear && QInside)
continue;
// 9. Compute the intersections I and J of PQ
// with ABC in 2D.
// If, in 3D, such an intersection lies in front of
// ABC, this triangle does not obscure PQ.
// Otherwise, the intersections lie behind ABC and
// this triangle obscures part of PQ:
double lambdaMin = 1.0, lambdaMax = 0.0;
for (int ii=0; ii<3; ii++)
{ double v1 = BScr.x - AScr.x, v2 = BScr.y - AScr.y,
w1 = AScr.x - PScr.x, w2 = AScr.y - PScr.y,
denom = u2 * v1 - u1 * v2;
if (denom != 0)
{ double mu = (u1 * w2 - u2 * w1)/denom;
// mu = 0 gives A and mu = 1 gives B.
if (mu > -0.0001 && mu < 1.0001)
{ double lambda = (v1 * w2 - v2 * w1)/denom;
// lambda = PI/PQ
// (I is point of intersection)
if (lambda > -0.0001 && lambda < 1.0001)
{ if (PInside != QInside &&
lambda > 0.0001 && lambda < 0.9999)
{ lambdaMin = lambdaMax = lambda;
break;
// Only one point of intersection
}
if (lambda < lambdaMin) lambdaMin = lambda;
if (lambda > lambdaMax) lambdaMax = lambda;
}
}
}
Point2D temp = AScr; AScr = BScr;
BScr = CScr; CScr = temp;
}
float d = obj.getD();
if (!PInside && lambdaMin > 0.001)
{ double IScrx = PScr.x + lambdaMin * u1,
IScry = PScr.y + lambdaMin * u2;
// Back from screen to eye coordinates:
double zI = 1/(lambdaMin/zQ + (1 - lambdaMin)/zP),
xI = -zI * IScrx / d, yI = -zI * IScry / d;
if (a * xI + b * yI + c * zI > h1) continue;
Point2D IScr = new Point2D((float)IScrx, (float)IScry);
if (Tools2D.distance2(IScr, PScr) >= 1.0)
lineSegment(g, Pe, new Point3D(xI, yI, zI), PScr,
IScr, iP, -1, i + 1);
}
if (!QInside && lambdaMax < 0.999)
{ double JScrx = PScr.x + lambdaMax * u1,
JScry = PScr.y + lambdaMax * u2;
double zJ =
1/(lambdaMax/zQ + (1 - lambdaMax)/zP),
xJ = -zJ * JScrx / d, yJ = -zJ * JScry / d;
if (a * xJ + b * yJ + c * zJ > h1) continue;
Point2D JScr = new Point2D((float)JScrx, (float)JScry);
if (Tools2D.distance2(JScr, QScr) >= 1.0)
lineSegment(g, Qe, new Point3D(xJ, yJ, zJ),
QScr, JScr, iQ, -1, i + 1);
}
return;
// if no continue-statement has been executed
}
drawLine(g, PScr.x, PScr.y, QScr.x, QScr.y);
}
}
I'm trying to merge a png with some transparency on a background:
Mat frg = Highgui.imread( "path/to/foreground/image.png" ), -1 );
Mat bkg = Highgui.imread( "path/to/background/image.png" ), -1 );
// Create mask from foreground.
Mat mask = new Mat( frg.width(), frg.height(), 24 );
double f[] = { 1, 1, 1, 0 };
double e[] = { 0, 0, 0, 0 };
for ( int y = 0; y < ( int )( frg.rows() ); ++y ) {
for ( int x = 0; x < ( int )( frg.cols() ); ++x ) {
double info[] = frg.get( y, x );
if ( info[3] > 0 ) {
mask.put( y, x, e );
} else {
mask.put( y, x, f );
}
}
}
// Copy foreground onto background using mask.
frg.copyTo( bkg, mask );
Highgui.imwrite( "path/to/result/image.png", bkg );
The resulting image is a kind of ghost showing the background image repeated various times and the foreground one in a corrupted way.
Any clues?
Thanks Rosa and for those interested, here is how I translated into Java the C++ snippet pointed out by Rosa:
private Mat overtrayImage( Mat background, Mat foreground ) {
// The background and the foreground are assumed to be of the same size.
Mat destination = new Mat( background.size(), background.type() );
for ( int y = 0; y < ( int )( background.rows() ); ++y ) {
for ( int x = 0; x < ( int )( background.cols() ); ++x ) {
double b[] = background.get( y, x );
double f[] = foreground.get( y, x );
double alpha = f[3] / 255.0;
double d[] = new double[3];
for ( int k = 0; k < 3; ++k ) {
d[k] = f[k] * alpha + b[k] * ( 1.0 - alpha );
}
destination.put( y, x, d );
}
}
return destination;
}