I'm using Processing to divide a large image into a series of smaller, rectangular nodes.
Processing stores the color value for the pixels of a PImage in a pixels array, which I am accessing to break up the image into smaller parts. For some reason, I am getting this output, when my intent was for the entire image to be displayed when the nodes are arranged in draw().
Here is my main class:
ArrayList node = new ArrayList();
PImage grid;
PVector nodeDimensions = new PVector(210, 185);
PVector gridDimensions = new PVector(2549, 3300);
String name = "gridscan.jpeg";
void setup() {
size(500, 500);
grid = loadImage(name);
grid.loadPixels();
fillPixels();
noLoop();
}
void fillPixels() {
int nodeNum = 0;
for (int startX = 0; startX < 2549 - nodeDimensions.x; startX += nodeDimensions.x) {
for (int startY = 0; startY < 3300 - nodeDimensions.y; startY += nodeDimensions.y) {
node.add(new Node());
sendPixels(new PVector(startX, startY), nodeNum);
nodeNum++;
}
}
}
void sendPixels(PVector start, int nodeNum) {
for (int x = int(start.x); x < start.x + nodeDimensions.x; x++) {
for (int y = int(start.y); y < start.x + nodeDimensions.y; y++) {
Node _node = (Node) node.get(node.size() - 1);
_node.fillPixel(new PVector(x, y), grid.pixels[int(y*gridDimensions.x+x)]);
}
}
}
void draw() {
drawNodes();
}
void drawNodes() {
int nodeNum = 0;
for (int x = 0; x < width; x += nodeDimensions.x) {
for (int y = 0; y < height; y += nodeDimensions.y) {
Node _node = (Node) node.get(nodeNum);
_node.drawMe(new PVector(x - (nodeDimensions.x/2), y - (nodeDimensions.y/2)));
nodeNum++;
}
}
}
And here is the Node class:
class Node {
color[] pixel;
Node() {
pixel = new color[int(nodeDimensions.x * nodeDimensions.y)];
}
void fillPixel(PVector pos, color pixelValue) {
if(int(pos.y * nodeDimensions.y + pos.x) < 38850) pixel[int(pos.y * nodeDimensions.y + pos.x)] = pixelValue;
}
void drawMe(PVector centerPos) {
pushMatrix();
translate(centerPos.x, centerPos.y);
for(int x = 0; x < nodeDimensions.x; x++) {
for(int y = 0; y < nodeDimensions.y; y++) {
stroke(getPixelColor(new PVector(x, y)));
point(x,y);
}
}
popMatrix();
}
color getPixelColor(PVector pos) {
return pixel[int(pos.y * nodeDimensions.x + pos.x)];
}
}
Hopefully my code makes sense. I suspect the issue is in the sendPixels() method of the main class.
I used this this page from the Processing reference as a guide for creating that function, and I'm not sure where my logic is wrong.
Any help would be appreciated, and please let me know if I can clarify something.
According to getPixelColor(), it seems that it uses rows.
So if you have a 5x5 square image then 2x2 would be 7.
To get the index you use this formula:
index = (y - 1) * width + x
Explained this way it's look pretty simple, doesn't it?
Alternatively, you may be able to use getSubimage() on the BufferedImage returned by the getImage method of PImage. There's a related example here.
Related
I am trying to make Space Invaders in Processing. I am currently working on getting the enemy to move correctly. I have got them to be drawn in the right spot but I haven't gotten them to be moved correctly. Here is my code:
PImage mainPlayer;
PImage enemyPlayer;
float Xspeed = 60;
float Yspeed = 60;
float X;
float Y;
Enemy EnemyPlayer = new Enemy("EnemyPlayerSpaceInvaders.png", 10, 10, 6);
void setup() {
size(1400, 800);
//enemyPlayer = loadImage("EnemyPlayerSpaceInvaders.png");
mainPlayer = loadImage("MainPlayerSpaceInvaders.png");
}
void draw() {
background(0);
Enemy[] enemyPlayer = new Enemy[60];
for (int i = 0; i < 5; i += 1) {
for (int j = 0; j < 11; j += 1) {
enemyPlayer[j *i] = new Enemy("EnemyPlayerSpaceInvaders.png", 50 + j * 100, 5 + 75 * i, 6);
}
}
for (int i = 0; i < 5; i += 1) {
for (int j = 0; j < 11; j += 1) {
if(enemyPlayer[j * i].alive){
enemyPlayer[j * i].Draw();
}
enemyPlayer[j *i].moveAndDraw(6);
}
}
}
class Enemy {
boolean alive = true;
float x;
float y;
float speed;
String playerTexFile;
PImage playerTex;
Enemy(String PlayerTexFile, float X, float Y, float Speed){
x = X;
y = Y;
speed = Speed;
playerTexFile = PlayerTexFile;
}
void Draw(){
playerTex = loadImage(playerTexFile);
image(playerTex, x, y);
}
void moveAndDraw(float Speed){
playerTex = loadImage(playerTexFile);
if(alive){
x += Speed;
if (x >= 1300) {
x = 100;
y += 50;
}
}
}
}
Here is my result:
The Draw function works but what you're seeing that is messing it up is the moveAndDraw() function.
And the enemy drawings aren't moving. I have made this before with c++ SFML but in that there is a very basic getPosition function. The reason I want to get position is that right now I'm having to use inaccurate numbers as the X and Y position and for the enemy to move correctly I need to know exactly what it's position is. I have checked multiple pages on processing.org but none of them helped. I haven't found any getPosition void and all the ones I've seen other people using a void to do this I just haven't been able to get it to work. If there is some code that could get me this to work or just some function I've looked over and even a website page I could look at I'd be open to it. Please tell me anything I can do to get this working.
The issue is that you recreate the enemies in every frame at it's initial position:
void draw() {
background(0);
Enemy[] enemyPlayer = new Enemy[60];
for (int i = 0; i < 5; i += 1) {
for (int j = 0; j < 11; j += 1) {
enemyPlayer[j *i] = new Enemy("EnemyPlayerSpaceInvaders.png", 50 + j * 100, 5 + 75 * i, 6);
}
}
// [...]
}
You've to:
Create a global array of enemies Enemy[] enemyPlayer (and delete PImage enemyPlayer).
Create and initialize the enemies in setup.
Use and move the existing enemies in draw:
Further note, that your loops doesn't do what you expect it to do. Create the enemies in 2 nested loops. If i runs from o to 6 and j from 0 to 10, the the index of an enemy is i*10 + j.
The enemies can be moved in a single loop from 0 to enemyPlayer.length.
//PImage enemyPlayer; <--- DELETE
// global array of enemies
Enemy[] enemyPlayer = new Enemy[60];
// [...]
void setup() {
size(1400, 800);
mainPlayer = loadImage("MainPlayerSpaceInvaders.png");
// create enemies
for (int i = 0; i < 6; i += 1) {
for (int j = 0; j < 10; j += 1) {
enemyPlayer[i*10 + j] = new Enemy("rocket64.png", 50 + j * 100, 5 + 75 * i, 6);
}
}
}
void draw() {
background(0);
// move enemies
for(int i = 0; i < enemyPlayer.length; ++i ) {
if(enemyPlayer[i].alive){
enemyPlayer[i].Draw();
}
enemyPlayer[i].moveAndDraw(6);
}
}
Basically, what I need to do is take a 2d array of bitflags and produce a list of 2d rectangles to fill the entire area with the minimum number of total shapes required to perfectly fill the space. I am doing this to convert a 2d top-down monochrome of a map into 2d rectangle shapes which perfectly represent the passed in image which will be used to generate a platform in a 3d world. I need to minimize the total number of shapes used, because each shape will represent a separate object, and flooding it with 1 unit sized squares for each pixel would be highly inefficient for that engine.
So far I have read in the image, processed it, and filled a two dimensional array of booleans which tells me if the pixel should be filled or unfilled, but I am unsure of the most efficient approach of continuing.
Here is what I have so far, as reference, if you aren't following:
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
int[] rgb = bi.getRGB(0, 0, bi.getWidth(), bi.getHeight(), new int[bi.getWidth() * bi.getHeight()], 0, bi.getWidth());
Origin origin = new Origin(bi.getWidth() / 2, bi.getHeight() / 2);
boolean[][] flags = new boolean[bi.getWidth()][bi.getHeight()];
for (int y = 0; y < bi.getHeight(); y++) {
for (int x = 0; x < bi.getWidth(); x++) {
int index = y * bi.getWidth() + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
List<Rectangle> list = new ArrayList();
//Fill list with rectangles
}
}
White represents no land. Black or Red represents land. The check for the red pixel marks the origin position of map, which was just for convenience and the rectangles will be offset by the origin position if it is found.
Edit: The processing script does not need to be fast, the produced list of rectangles will be dumped and that will be what will be imported and used later, so the processing of the image does not need to be particularly optimized, it doesn't make a difference.
I also just realized that expecting a 'perfect' solution is expecting too much, since this would qualify as a 'knapsack problem' of the multidimensionally constrained variety, if I am expecting exactly the fewest number of rectangles, so simply an algorithm that produces a minimal number of rectangles will suffice.
Here is a reference image for completion:
Edit 2: It doesn't look like this is such an easy thing to answer given no feedback yet, but I have started making progress, but I am sure I am missing something that would vastly reduce the number of rectangles. Here is the updated progress:
static int mapWidth;
static int mapHeight;
public static void main(String[] args) {
File file = new File(args[0]);
BufferedImage bi = null;
System.out.println("Reading image...");
try {
bi = ImageIO.read(file);
} catch (IOException ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
if (bi != null) {
System.out.println("Complete!");
System.out.println("Interpreting image...");
mapWidth = bi.getWidth();
mapHeight = bi.getHeight();;
int[] rgb = bi.getRGB(0, 0, mapWidth, mapHeight, new int[mapWidth * mapHeight], 0, mapWidth);
Origin origin = new Origin(mapWidth / 2, mapHeight / 2);
boolean[][] flags = new boolean[mapWidth][mapHeight];
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
int index = y * mapWidth + x;
int color = rgb[index];
int type = color == Color.WHITE.getRGB() ? 1 : (color == Color.RED.getRGB() ? 2 : 0);
if (type == 2) {
origin = new Origin(x, y);
}
flags[x][y] = type != 1;
}
}
System.out.println("Complete!");
System.out.println("Processing...");
//Get Rectangles to fill space...
List<Rectangle> rectangles = getRectangles(flags, origin);
System.out.println("Complete!");
float rectangleCount = rectangles.size();
float totalCount = mapHeight * mapWidth;
System.out.println("Total units: " + (int)totalCount);
System.out.println("Total rectangles: " + (int)rectangleCount);
System.out.println("Rectangle reduction factor: " + ((1 - rectangleCount / totalCount) * 100.0) + "%");
System.out.println("Dumping data...");
try {
file = new File(file.getParentFile(), file.getName() + "_Rectangle_Data.txt");
if(file.exists()){
file.delete();
}
file.createNewFile();
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file)));
for(Rectangle rect: rectangles){
bw.write(rect.x + "," + rect.y + "," + rect.width + ","+ rect.height + "\n");
}
bw.flush();
bw.close();
} catch (Exception ex) {
Logger.global.log(Level.SEVERE, null, ex);
}
System.out.println("Complete!");
}else{
System.out.println("Error!");
}
}
public static void clearRange(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
flags[x][y] = false;
}
}
}
public static boolean checkIfFilled(boolean[][] flags, int xOff, int yOff, int width, int height) {
for (int y = yOff; y < yOff + height; y++) {
for (int x = xOff; x < xOff + width; x++) {
if (!flags[x][y]) {
return false;
}
}
}
return true;
}
public static List<Rectangle> getRectangles(boolean[][] flags, Origin origin) {
List<Rectangle> rectangles = new ArrayList();
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
if (flags[x][y]) {
int maxWidth = 1;
int maxHeight = 1;
Loop:
//The search size limited to 400x400 so it will complete some time this century.
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
//Search also in the opposite direction
Loop:
for (int h = Math.min(400, mapHeight - y); h > 1; h--) {
for (int w = Math.min(400, mapWidth - x); w > 1; w--) {
if (w * h > maxWidth * maxHeight) {
if (checkIfFilled(flags, x, y, w, h)) {
maxWidth = w;
maxHeight = h;
break Loop;
}
}
}
}
rectangles.add(new Rectangle(x - origin.x, y - origin.y, maxWidth, maxHeight));
clearRange(flags, x, y, maxWidth, maxHeight);
}
}
}
return rectangles;
}
My current code's search for larger rectangles is limited to 400x400 to speed up testing, and outputs 17,979 rectangles, which is a 99.9058% total reduction of rectangles if I treated each pixel as a 1x1 square(19,095,720 pixels). So far so good.
I am creating a battle ship game, where each round the player can select 4 ship routes by selecting the 4 closest tiles to him, with a mouse.
Now in the following gif, you can see if I will hover on the very edge of a tile, it will select two tiles at once, or even 4 at once if you're holding your mouse in middle of 4 tiles:
(source: gyazo.com)
This should not really be happening in the game, how can I prevent that?
This is the updating code:
/**
*
* #param x mouse X
* #param y mouse Y
*/
public void checkHover(int x, int y) {
for (int i = 0; i < tiles[0].length; i++) {
for (int j = 0; j < tiles[1].length; j++) {
// get coordinates from tile
int x1 = i * (sprite.getWidth() - 1);
int y1 = j * (sprite.getHeight() - 1);
// If we have screen translate, we can cancel it for
// this situation
int realX = x - this.translate.getX();
int realY = y - this.translate.getY();
// checking if mouse inside tile
if (realX >= x1 && realX <= x1 + sprite.getWidth() &&
realY >= y1 && realY <= y1 + sprite.getHeight()) {
// set tile to hovered sprite
this.tiles[i][j].setHover(true);
}
else {
// cancel hovered sprite
this.tiles[i][j].setHover(false);
}
}
}
}
How can I prevent that?
Instead of the two loops I would calculate the index of the 2d array based off the x and y position of the mouse, by dividing the x and y position by how much space there is from one tile to the next (which looks to be the tile width plus 1, the empty border). This will cause border hovers to favor a certain side, but it looks like you don't want to select two tiles anyways.
Once you have the index of the tile, switch the highlight on, and keep a reference to it with an instance variable like lastHighlightedTile, but before you do that, also call lastHighlightedTile.setHover(false);.
If the index of the tile is invalid, as if the mouse is out of the tile area, don't access the array and still call lastHighlightedTile.setHover(false);.
This should fix highlighting multiple tiles, and will also allow you to turn off the previous highlight (the solution of breaking out of the current loop might not turn off the previous highlight).
Assuming width of sprite is equal to 50, I see that first title has x1 to x2 coordinate (0 - 50), second title has (49 - 100), third has (99 - 150)... Same for y dimension.
So when pointer is at (x) = (50), it hovers (x1 - x2) (0-50) and (49-100) titles.
Why are you subtracting 1 from sprite width / height?
try with this:
// get coordinates from tile
int x1 = i * (sprite.getWidth());
int y1 = j * (sprite.getHeight());
Edit: I wrote sample app, which shoved me solution:
public class App {
Tile[][] tiles;
Sprite sprite;
public static void main(String[] args) {
App app = new App();
app.init();
app.checkHover(50, 50);
app.printHovers();
}
private void init() {
sprite = new Sprite();
tiles = new Tile[10][10];
for (int i = 0; i < tiles[0].length; i++) {
for (int j = 0; j < tiles[1].length; j++) {
tiles[i][j] = new Tile();
}
}
}
public void checkHover(int x, int y) {
for (int i = 0; i < tiles[0].length; i++) {
for (int j = 0; j < tiles[i].length; j++) {
// get coordinates from tile
int x1 = i * (sprite.getWidth());
int y1 = j * (sprite.getHeight());
// If we have screen translate, we can cancel it for
// this situation
// int realX = x - this.translate.getX();
// int realY = y - this.translate.getY();
int realX = x;
int realY = y;
// checking if mouse inside tile
if (realX >= x1 && realX < x1 + sprite.getWidth()
&& realY >= y1 && realY < y1 + sprite.getHeight()) {
// set tile to hovered sprite
this.tiles[i][j].setHover(true);
} else {
// cancel hovered sprite
this.tiles[i][j].setHover(false);
}
}
}
}
public void printHovers() {
for (int i = 0; i < tiles[0].length; i++) {
for (int j = 0; j < tiles[i].length; j++) {
System.out.print((tiles[i][j].isHover() ? "Y" : "O") + " ");
}
System.out.println();
}
}
public class Sprite {
public int getWidth() {
return 50;
}
public int getHeight() {
return 50;
}
}
public class Tile {
private boolean hover;
public boolean isHover() {
return hover;
}
public void setHover(boolean hover) {
this.hover = hover;
}
}
}
It works fine for (49, 49), (49, 50), (50, 49) and (50, 50) coords.
So, first you have to remove -1 subtractions, and change <= test to <.
EDIT: Different solution:
Simply declare a boolean at the beginning of the method, and set only if that boolean is still false. Once you set the tile on, also set the boolean to true, which will prevent others from being set on.
public void checkHover(int x, int y) {
boolean hasTurnedOneOn = false;
for (int i = 0; i < tiles[0].length; i++) {
for (int j = 0; j < tiles[1].length; j++) {
// get coordinates from tile
int x1 = i * (sprite.getWidth() - 1);
int y1 = j * (sprite.getHeight() - 1);
// If we have screen translate, we can cancel it for
// this situation
int realX = x - this.translate.getX();
int realY = y - this.translate.getY();
// checking if mouse inside tile
if (realX >= x1 && realX <= x1 + sprite.getWidth() &&
realY >= y1 && realY <= y1 + sprite.getHeight() && !hasTurnedOneOn) {
// set tile to hovered sprite
this.tiles[i][j].setHover(true);
hasTurnedOneOn = true;
}
else {
// cancel hovered sprite
this.tiles[i][j].setHover(false);
}
}
}
}
I have a chunk of cubes that is generated using a display list, and ever time I come near it, the frame rate drops significantly. Why is this? Here's some code:
public class Chunk implements GameObject {
private int sx, sy, sz, lx, ly, lz, vertID;
private Tile[][][] tiles;
public Chunk(int sx, int sy, int sz) {
this.sx = sx;
this.sy = sy;
this.sz = sz;
this.lx = sx + 16;
this.ly = sy + 16;
this.lz = sz + 16;
init();
}
#Override
public void init() {
this.tiles = new Tile[lx][ly][lz];
vertID = glGenLists(1);
glNewList(vertID, GL_COMPILE);
for (int x = sx; x < lx; x++) {
for (int y = sy; y < ly; y++) {
for (int z = sz; z < lz; z++) {
tiles[x][y][z] = new Tile("grass.jpg");
}
}
}
glEndList();
}
public void rebuild() {
glNewList(vertID, GL_COMPILE);
for (int x = sx; x < lx; x++) {
for (int y = sy; y < ly; y++) {
for (int z = sz; z < lz; z++) {
tiles[x][y][z].getVertices(x, y, z, 16);
}
}
}
glEndList();
}
#Override
public void update() {
}
#Override
public void render() {
glCallList(vertID);
}
#Override
public void dispose() {
}
}
Your problem lies probably herein:
glNewList(vertID, GL_COMPILE);
for (int x = sx; x < lx; x++) {
for (int y = sy; y < ly; y++) {
for (int z = sz; z < lz; z++) {
tiles[x][y][z] = new Tile("grass.jpg");
}
}
}
glEndList();
If I had to make an educated guess your Tile class generate and initialiizes a texture. Well, you see, display lists don't just take on geometry and drawing calls. They also encapsulate the creation and setting of texture data. Before OpenGL-1.1 (i.e. OpenGL-1.0) there were no texture objects (glGenTextures, glBindTexture) and you used display lists as a drop in replacement for that. And that little anachronism is still present today in all compatibility OpenGL profiles. That's also why Windows calls the function to share OpenGL context data wglShareLists, although it goes far beyond lists.
To make a long story short, you probably perform a quite expensive operation right in the middle of a display list. No wonder it's slow. But seriously, why are you allocating a new texture for each tile? You should load your images into your textures only once and then only make references to them.
I'm having trouble getting my method to work. The method should mirror any image I choose on its diagonal to produce a mirror effect, but at the moment it just produces the same image unedited and I don't what I'm doing wrong. Any help would be greatly appreciated. Thank you.
public Picture mirrorImageDiagonal() {
int size = this.getWidth();
Pixel rightPixel = null;
Pixel leftTargetPixel = null;
Pixel rightTargetPixel = null;
Picture target = new Picture(size, size);
for (double x = 0; x < size; x ++) {
for (double y = 0; y <= x; y ++) {
int yIndex = Math.min((int) y, this.getHeight() - 1);
int xIndex = Math.min((int) x, this.getWidth() - 1);
leftTargetPixel = target.getPixel(yIndex, xIndex);
rightTargetPixel = target.getPixel(xIndex, yIndex);
rightPixel = this.getPixel(xIndex, yIndex);
rightTargetPixel.setColor(rightPixel.getColor());
leftTargetPixel.setColor(rightPixel.getColor());
}
}
return target;
}
I am assuming that you are trying to complete the challenge for A6 in the picture lab packet. I just completed this for school, but if you are not, I hope this still helps you.
public void mirrorDiagonal()
{
Pixel[][] pixels = this.getPixels2D();
Pixel pixel1 = null;
Pixel pixel2 = null;
int width = pixels[0].length;
for (int row = 0; row < pixels.length; row++)
{
for (int col = 0; col < width; col++)
{
if (col < pixels.length)
{
pixel1 = pixels[row][col];
pixel2 = pixels[col][row];
pixel1.setColor(pixel2.getColor());
}
}
}
}