Loop which creates "windows" in buildings? - Java Swing - java

I am a beginner with Java and especially Java Swing. What I am trying to do is create randomly generated "buildings" (which I have done) and on top of them add windows in rows/columns that are equally sized and spaced apart. However I want to do this in a loop (for loop?). My end goal is to also only have a few of the windows light up each time (this also being randomly generated) the code is run. Here is what I have so far, the window I have created is basically the size I want them to be. I know my attribute maxX is not used but I have created it to remind me of the max X value and in case I need it later.
import java.awt.*;
import javax.swing.*;
public class JPanelExample extends JPanel
{
private int maxX = 784;
private int maxY = 712;
#Override
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.WHITE);
g2d.fillRect(5, 5, 25, 25);
int width = (int)(Math.random()*100+100);
int height = (int)(Math.random()*350+100);
for (int i =10; i<634; i+=(width+10))
{
for (int j = 462 ; j>=462; j--)
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY-height, width, height);
g2d.fillRect(i, maxY-height, width, height);
g2d.setColor(Color.YELLOW);
g2d.drawRect(i+5, (maxY-height)+5, width/6, width/6);
g2d.fillRect(i+5, (maxY-height)+5, width/6, width/6);
height = (int)(Math.random()*462+100);
while (width == i+(width+10))
{
width = (int)(Math.random()*100+100);
}
}
}
}
public static void main(String[] args)
{
JFrame frame = new JFrame("Frame");
frame.add(new JPanelExample());
frame.setBackground(Color.BLACK);
frame.setSize(800, 750);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}

I hope you're having fun with Java and if you're still looking for an answer (though there are many) here's my suggestion:
In order to get a row of windows on the buildings you've drawn here, you need to know:
how many windows you'll be drawing (from your code, it looks like you always want 6 windows)
where to begin drawing (from your code, this seems to be 5px after the start of each building
how much spacing between windows you want (this isn't evident in your code, but it can be whatever you want -- for this example we will use 5px again)
what colour the window should be (for this example we will use white for no-light and yellow for light turned on).
Set Up
We can store all these numbers in variables like this (using values from your code):
// for the lights:
Color off = Color.WHITE;
Color on = Color.YELLOW;
// for window calculations
int space = 5;
int startx = i+space;
int starty = maxY-height+space;
int interval = (width-space)/6;
Note that we subtract one space from the building's width before dividing by 6 to discount the initial space before the first window. I'm sure you've seen this already, but drawing diagrams can also be very helpful when starting with Java/Swing
The Loop
We can now loop through the number of windows you'd like to place in that row of windows. In this case, it looks like you'll want 6 windows every time. For each window (inside the for-loop) we first want to decide on the color (in the example we use a 10% chance of the lights being on, as noted below). Once we've set the colour, we simply use the values we've noted above to fill the next window rectangle, which is explained below. Make sure your for-loops have different variable names, since we have them nested here
for(int k = 0; k < 6; k++) {
if((int)(Math.random()*100) < 10) //10% of the time, turn the light color on
g2d.setColor(on);
else
g2d.setColor(off);
//g2d.drawRect(i+5, (maxY-height)+5, width/6, width/6);
g2d.fillRect(startx+(k*interval), starty, (interval)-space, (interval)-space);
}
As a note: the drawRect followed by fillRect here are redundant. I'm not sure if you have a specific purpose for drawing the rect first (in which case, ignore this), but view your app with the drawRect commented out to see what it does.
If you have any trouble with the Math.random function here, check out these resources:
http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html
Math.random() explained
Exploring the fillRect in our example:
fillRect takes a few variables noted below (details at: http://docs.oracle.com/javase/7/docs/api/java/awt/Graphics.html)
fillRect(int x, int y, int width, int height)
In our example we've set the following:
x = startx + (k * interval) , k being the incremental variable for the for loop (runs from 1 .. 6), increasing the x value for the rectangle by a window's interval after each window drawn.
y = starty , this is consistent for each row.
width, height = interval - space , this sets the dimensions of the window and ensures a proper space before the next window.
This should give you a working 1 row of windows.
Columns
Getting columns of windows is very similar to getting the row. There are many ways to do all of these things, but I'll briefly go through another nested-for-loop.
In order to get columns of windows we need to know:
How many rows you want to have (how many windows per column)
Because we've already set up window dimensions we can determine the number of windows that can fit in a column using the following:
int num_rows = (height - space) / interval;
//and to set a maximum number of rows to a column (ex: 6)
int num_rows = Math.min(((height-space) / interval), 6); //a max number can replace 6 here
Here we'll use another for-loop to continuously add rows, num_rows times. Note: we've changed fillRect such that y = starty + (l*interval) , incrementing the start position y to the next location of rows each time one row is drawn.
for(int l = 0; l < num_rows; l++){
for(int k = 0; k < 6; k++) {
if((int)(Math.random()*100) < 10) //10% of the time, turn the light color on
g2d.setColor(on);
else
g2d.setColor(off);
g2d.fillRect(startx+(k*interval), starty + (l*interval), (interval)-space, (interval)-space);
}
}
And that's that. Hope that's helpful and not too confusing.

After a long time, I figured out a solution. I have changed the for-loop to this:
for (int i =10; i<634; i+=(a+10))//buildings
{
g2d.setColor(Color.GRAY);
g2d.drawRect(i, maxY-height, width, height);
g2d.fillRect(i, maxY-height, width, height);
rows = Math.round((height)/25);
columns = Math.round(width/25);
for (int j = 1; j<=columns; j++)//windows
{
for (int k = 1; k<=rows; k++)
{
g2d.setColor(Color.BLACK);
g2d.drawRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
if (Math.random()<0.7)
{
g2d.setColor(Color.YELLOW);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
else
{
g2d.setColor(transYellow);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
g2d.setColor(transYellow);
g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
}
}
}
addBuilding();
a = width;
height = (int)(Math.random()*462+100);
width = (int)(Math.random()*100+100);
}

Related

How can I plot in Java using Graphics2d.setPaint() fast for large data set

I have a large set of data I wish to plot on a graph that can range from 10k points to about 20 Million Points. At 10k points the plot happens at an ok speed(within a second), but at 20 Million points the plot seems to take minutes.
I'm using Java for the program and rewriting the code in another language just to improve the graphical plotting speed for one single plot that only occurs at at maximum data set is not in the cards.
Is this speed something I have to live with because a 20 Million point plot inherently will take this long due to the data size or am I missing out on some optimisation flag/method/etc?
My Data is in a 2d Array of 13,000 by 4096 called Data.
This is populated from outside the Plot Function in Main.java
//In Plot.java
public class PlotG extends JPanel
{
double xscale = 0.0;
double yscale = 0.0;
protected void paintComponent(Graphics g)
{
super.paintCompnent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHint.Key_ANTIALIASING, RenderingHint.VALUE_ANTIALIAS_ON);
//Scaling
int sizew = Data.size();
int sizeh = Data.get(0).size();
xscale = (getWidth()*1.0)/(sizew *1.0);
yscale = (getHeight()*1.0)/(sizeh *1.0);
//Set Colour
g2.setPaint(Color.GREEN);
//Plot
for(int j=0; j<sizew; j++)
{
for(int k=0;k<sizeh; k++)
{
if(Data.get(j).get(k) > MinimumValueToPlot) //I only plot points above the constant value MinimumValueToPlot
{
int x = xscale*j;
int y = yscale*k;
g2.fillOval(x,y,1,1);
}
}
}
return;
}
}
private Plot dataPlot = new Plot()
public PlotStuff(ArrayList<ArrayList<Double>> In)
{
Data = In;
InitPLot(getContentPane());
}
private void InitPlot(Container contentPane)
{
getContentPane().setBackground(Color.GRAY);
getContentPane().setLayout(new FlowLayout(FlowLayout.LEADING));
setMinimumSize(new Dimension(1650, 830));
pack();
GraphPanel = new JPanel();
GraphPanel.setBounds(6,11,1470,750);
GraphPanel.setBorder( BorderFactory.createTitleBorder( BorderFactory.createLineBorder(Color.GREEN, 2),
"Title",
TitledBorder.DEFAULT_JUSTIFICATION,
TitledBorder.DEFAULT_POSITION,
new Font("Tahoma", Font.BOLD, 18),
Color.WHITE));
getContentPanel().add(GraphPanel);
GraphPanel.setLayout(new BorderLayout());
dataPlot.setBackGround(Color.BLUE);
dataPlot.setForeGround(Color.WHITE);
dataPlot.setPreferredSize(new Dimension(1470, 750));
GraphPanel.add(dataPlot);
return;
}
//in Main.java
.
.
PlotStuff p = new PlotStuff(Data);
p.redrawGraph();
p.setVisible();
.
.
Is there anyway to improve the speed if the number of points above my constant MinimumValueToPlot reaches 20 Million and above? Given my maximum possible data set is 13k x 4096 = 53,248,000, it is possible, but the highest experienced number of points above MinimumValueToPlot is so far only 20 Million.
Am I doing something wrong in the JPanel declarations? I have seen some discussions say that setPreferredSize shouldn't be used? Is this the case?
Thanks.
You claim Data is a 2d array, but it is not. It is an ArrayList of ArrayLists. Work with an actual array instead.
Instead of ArrayList<ArrayList<Double>> you'd do double[][].
In your loop you are calling fillOval to set the color of a single pixel. That causes huge unneccessary overhead. You are basically telling your application to calculate the pixels for an oval shape of size 1x1 20 million times!
I suggest you create a BufferedImage, get its pixel array, and set the pixel values directly. Then, when done, draw that image to your Graphics2d object:
BufferedImage img = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_3BYTE_BGR);
Graphics2D imgGraphics2D = img.createGraphics();
imgGraphics2D.setColor(Color.white);
imgGraphics2D.fillRect(0, 0, getWidth(), getHeight());
imgGraphics2D.dispose();
byte[] pixels = ((DataBufferByte) img.getRaster().getDataBuffer()).getData();
for(int j=0; j < sizew; j++)
{
for(int k=0; k < sizeh; k++)
{
if(data[j][k] > minimumValueToPlot)
{
int x = (int)(xscale * j);
int y = (int)(yscale * k);
int pixelindex = (y * getWidth() + x) * 3;
pixels[pixelindex + 1] = 255; // set the green byte
}
}
}
g2.setComposite(AlphaComposite.Src);
g2.drawImage(img, 0, 0, null);
Please take this with a grain of salt, as I wrote this from memory.
(Also I took the liberty of renaming Data to data and MinimumValueToPlot to minimumValueToPlot. Per convention variables in Java start with a lower case letter to distinguish them from classes.)
Plotting points like that in your paint components means that you paint that many points every time the component is repainted This means it is an expensive operation to resize the window, or click a button because it has to repaint every point.
In this version, I've made a method to redo the graphics, and and the paintComponent just repaints the backing buffer. Note, that painting to the backing buffer will be about as fast as you can accomplish paint, and it ignores the display properties of swing. That way you can use a profiler and see how fast you can make the painting routine go. For example using some of the suggestions of Max above.
public class PlotG extends JPanel
{
BufferedImage buffer;
double xscale = 0.0;
double yscale = 0.0;
public PlotG(){
buffer = new BufferedImage(1470, 750, BufferedImage.TYPE_INT_ARGB);
}
protected void paintComponent(Graphics g)
{
super.paintCompnent(g);
g.drawImage(buffer, 0, 0, this);
}
void updateBuffer(){
Graphics2D g2 = (Graphics2D)backing.getGraphics();
g2.setRenderingHint(RenderingHint.Key_ANTIALIASING, RenderingHint.VALUE_ANTIALIAS_ON);
//Scaling
int sizew = Data.size();
int sizeh = Data.get(0).size();
xscale = (getWidth()*1.0)/(sizew *1.0);
yscale = (getHeight()*1.0)/(sizeh *1.0);
//Set Colour
g2.setPaint(Color.GREEN);
//Plot
for(int j=0; j<sizew; j++)
{
for(int k=0;k<sizeh; k++)
{
if(Data.get(j).get(k) > MinimumValueToPlot) //I only plot points above the constant value MinimumValueToPlot
{
int x = xscale*j;
int y = yscale*k;
g2.fillOval(x,y,1,1);
}
}
}
repaint();
}
public Dimension getPreferredSize(){
return new Dimension(buffer.getWidth(this), buffer.getHeight(this));
}
}
The problem with setPreferredSize, is not a performance issue, it can conflict with swings swings layout managers. When you have a component, such as your custom graphing panel, you do want to have it determine the size. I've added it to the PlotG class, so now it will try to layout according to the backing buffer.
The you have to update the buffer, I suggest doing it on the main thread after you've set your panel to visible. That way it won't block the edt.

processing: trouble using global array in if condition in for loop

I am trying to write a small program that has a given number of balls (in the example code below it's 3) travel back and forth across the screen at different speeds and phases (start offset).
This much has been achieved in the code. Although I want to be able to select the balls (one at a time) using a mouse click.
I have used the word "HIT!!!" to signify in the console that a ball has been clicked.
My problem is that when I run the code below, I only get a "HIT!" in the console when I click the top ball. That is when the first element y[0] matches with the click_Y variable. When I am sure (but obviously mistaken somehow) that there should be matches when I click in the vicinity of y[1] & y[2].
I'd really be grateful for any help with these. As it's gotten to the point where I am starting to stare blankly at the screen. Thanks.
int noCircles; // the number of items in the array (# of circles)
float[] y; // y-position of each circle (fixed)
float[] speed; // speed of each circle
float[] phase; // phase of each circle
float red = 120;
float green = 120;
float blue = 120;
float click_X;
float click_Y;
void setup() {
size(500, 500);
noCircles = 3;
// allocate space for each array
y = new float[noCircles];
speed = new float[noCircles];
phase = new float[noCircles];
// calculate the vertical gap between each circle based on the total number
// of circles
float gap = height / (noCircles + 1);
//setup an initial value for each item in the array
for (int i=0; i<noCircles; i++) {
y[i] = gap * (i + 1);
// y is constant for each so can be calculated once
speed[i] = random(10);
phase[i] = random(TWO_PI);
}
}
void draw() {
background(155);
for (int i=0; i<noCircles; i++) {
// calculate the x-position of each ball based on the speed, phase and
//current frame
float x = width/2 + sin(radians(frameCount*speed[i] ) + phase[i])* 200;
if (dist(x, y[i], click_X, click_Y) <= 20){
println("HIT!!!!!!!!!!!!!!!!!!");
}
ellipse(x, y[i], 20, 20);
click_X = 0;
click_Y = 0;
}
}
void mousePressed() {
println("You clicked******************************************");
click_X = mouseX;
click_Y = mouseY;
println("click_X =" + click_X);
println("click_Y =" + click_Y);
}
Problems like these are best solved by debugging your program. Start by tracing through the code by hand, then add print statements (more than you've already added), and if that doesn't work then don't be afraid to use the debugger.
You're using the click_X and click_Y variables to check the position of the mouse against the position of each ball. Trace through the for loop in your draw() function. What happens at the end of the first iteration?
You reset the values of click_X and click_Y. That's why you aren't detecting any hits on the other circles.
You could probably refactor your code to only reset those variables if something has been hit, but really, I would stop using them altogether.
I'm guessing that you're using those variables because you only want to check when the mouse is pressed? Just use the mousePressed variable for that. Then you can use the mouseX and mouseY variables directly.
Then your if statement would look like this:
if (mousePressed && dist(x, y[i], mouseX, mouseY) <= 20) {
println("HIT: " + i);
}
Also, using separate arrays like this is called parallel arrays, and is general a bad habit to get into. You should probably use classes instead.

Swing drawing sometimes works

I'm trying to draw functions using Java Swing and AWT. The problem is not always all of the 300 points of the graph are drawn. When I loop over the first points of the graph in debug mode, there is much more change the graph is drawn completely. I use the following code to create a JFrame and set the graphics object to the class member g.
jFrame = new JFrame();
jFrame.setSize(WIDTH, HEIGHT);
jFrame.setVisible(true);
g = jFrame.getContentPane().getGraphics();
Then I call this method for every function I want to draw.
private void drawGraph(IGraph graph, Bounds bounds, Ratios ratios) {
//contains visual information about the graph
GraphVisuals visuals = graph.getVisuals();
g.setColor(visuals.color);
//the previous point is remembered, to be able to draw a line from one point to the next
int previousXi = 0;
int previousYi = 0;
//a loop over every point of the graph. The graph object contains two arrays: the x values and the y values
for (int i = 0; i < graph.getSize(); ++i) {
//calculate the x value using the ratio between the graph's size on the x-axis and the window size and the starting point on the x-axis
int xi = (int) (ratios.xRatio * (graph.getX(i) - bounds.xMin) + 0.5);
//analogous for the y axis
int yi = HEIGHT - (int) (ratios.yRatio * (graph.getY(i) - bounds.yMin) + 0.5);
//draw
if (visuals.hasBullets) {
g.fillOval(xi, yi, visuals.bulletSize, visuals.bulletSize);
}
if (visuals.hasLine) {
if (i != 0) {
g.drawLine(previousXi, previousYi, xi, yi);
}
}
previousXi = xi;
previousYi = yi;
}
}

Webcam Pixel Manipulation/Sorting via Processing

I am attempting to write a Processing sketch that will take each row's center pixel's color and apply that color to the entire row. However, I am having trouble with even getting the pixels to change. It seems like the sketch doesn't even go through the for-loops where I am trying to change the pixels because it doesn't print out any of the statements except the end draw at the end of draw(). I just end up with an unmanipulated feed. Does anyone know why this isn't working?
Also, currently using Processing's standard Video library with Capture at the moment, but if there is a better library that I could utilize please let me know! Thanks!
UPDATE: Testing out my algorithm with an array of numbers, it seems like using an inner for loop isn't working like how I thought it should. The i of the outer-loop is only incremented once after the first time the inner for loop completes itself, and then it just exits the outer loop instead of starting the inner loop again. What's going on here?
import processing.video.*;
Capture feed; // webcam
int pixelCount = width * height; // total # of pixels
int center = width / 2; // value for center pixel
int widthPlus = width + 1; // value to go row-to-row
color c; // center pixel color
void setup(){
size(displayWidth, displayHeight);
feed = new Capture(this);
feed.start();
}
void draw(){
if (feed.available() == true){
feed.read();
}
image(feed, 0, 0);
feed.loadPixels(); // load pixels from webcam
/** Use to look at each row one at a time*/
for (int i = 0; i < pixelCount; i+=widthPlus){
println("Outer for-loop");
c = feed.pixels[i + center]; //get center pixel
/** Make each pixel in row the color of 'c' */
for (int j = i; j < width; j++){
println("Inner for-loop");
feed.pixels[j] = c; // set pixel to 'c'
println(i + " - " + j);
}
}
feed.updatePixels(); // update pixels from webcam
println("end draw");
}

Java: Composite

I'm implementing a diagram that shows the level of a container. Depending on the fill level, the colour of the line should change (for instance, close to the maximum it should show red). Rather than calculating different segments of the line and setting their colours manually, I'd like to define a band in which the colour automatically changes. I thought to do this with a custom Composite/CompositeContext, but I seem not to be able to work out the locations of the pixels returned by the raster. My idea is to check for their y-Values and change the colour if a colour value is defined in the source and if the y-Value exceeds a threshold value.
My CompositeContext looks like this:
CompositeContext context = new CompositeContext() {
#Override
public void compose(Raster src, Raster dstIn, WritableRaster dstOut) {
int width = Math.min(src.getWidth(), dstIn.getWidth());
int height = Math.min(src.getHeight(), dstIn.getHeight());
int[] dstPixels = new int[width];
for (int y = 0; y < height; y++) {
dstIn.getDataElements(0, y, width, 1, dstPixels);
for (int x = 0; x < width; x++) {
if ( y ??? > 50) {
dstPixels[x] = 1;
} else {
// copy pixels from src
}
}
dstOut.setDataElements(0, y, width, 1, dstPixels);
}
}
"y" seems to be related to something, but it does not contain the absolute y-Value (in fact the compose method is called several times with 32x32 rasters). Maybe someone knows how to retrieve the position on the component or even a better way to define an area in which a given pixel value is replaced by another value.
Can't you just fill with a gradient with 0 alpha and then draw the line with full alpha?

Categories

Resources