Best way to fill shapes in Java (no recursion) - java

I have a BufferedImage whit some shapes drawn down on it, I'm trying to fill those whit a random color; I have made a method whit recursion to do this job, and it does it, but:
It's painfully slow
It requires a lot of stack memory (I had to scale it up to 1GB to avoid problems)
I heard that is always possible to avoid recursion but I can't come up whit a more efficient way to do it, any help would be appreciated
The method returns an ArrayList of int[] which represent all the pixel that I have to fill (then I color those pixel by pixel) It start from a random point that hasn't be colored yet.
I would like to have a result similar to the one that we can have using the instrument "fill" on Microsoft Paint
Here's the code of the method that find all points in a section:
ArrayList<int[]> populatesSection(ArrayList<int[]> section, int[] is) {
int[][] neighbors=new int[4][2];
int i;
neighbors[0][0]=is[0];
neighbors[0][1]=is[1]+1;
neighbors[1][0]=is[0]-1;
neighbors[1][1]=is[1];
neighbors[2][0]=is[0];
neighbors[2][1]=is[1]-1;
neighbors[3][0]=is[0]+1;
neighbors[3][1]=is[1];
toBeColored.remove(contains(toBeColored, is));
section.add(is);
for(i=0;i<neighbors.length;i++)
if(isValid(neighbors[i]) && contains(toBeColored, neighbors[i])>=0 && contains(section, neighbors[i])<0)
populatesSection(section, neighbors[i]);
return section;
}
initial BufferedImage:
colored BufferedImage:

I borrowed the flood method from camickr'a answer and enhanced it to auto flood fill the entire image.
I also took the long flood-related calculations off the EDT by performing it on a SwingWorker:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
public class FloodFill extends JPanel {
private final BufferedImage image;
private static final Color background = Color.WHITE;
private final Random rnd = new Random();
FloodFill(BufferedImage image) {
this.image = image;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(image.getWidth(), image.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, 0,0, this);
}
private void autoFloodFill(){
//take long process off the EDT by delegating it to a worker
new FloodFillWorker().execute();
}
private Optional<Point> findBackgrounPoint() {
int backgroundRGB = background.getRGB();
for (int x = 0; x < image.getWidth(); x++) {
for (int y = 0; y < image.getHeight(); y++) {
int imageRGB = image.getRGB(x, y);
if(imageRGB == backgroundRGB)
return Optional.of(new Point(x, y));
}
}
return Optional.empty();
}
private Color randomColor() {
return new Color(rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
}
class FloodFillWorker extends SwingWorker<Void, Void>{
//todo set sleep to 0
private final long SLEEP = 500; //used to slow sown for demo purposed.
#Override
protected Void doInBackground() throws Exception {
Optional<Point> backgroundPoint = findBackgrounPoint();
while(backgroundPoint.isPresent()){
floodFill(backgroundPoint.get(), randomColor());
SwingUtilities.invokeLater(()-> repaint()); //invoke repaint on EDT
backgroundPoint = findBackgrounPoint(); //find next point
Thread.sleep(SLEEP);
}
return null;
}
private void floodFill(Point point, Color replacement) {
int width = image.getWidth();
int height = image.getHeight();
int targetRGB = image.getRGB(point.x, point.y);
int replacementRGB = replacement.getRGB();
Queue<Point> queue = new LinkedList<>();
queue.add( point );
while (!queue.isEmpty()){
Point p = queue.remove();
int imageRGB = image.getRGB(p.x, p.y);
if (imageRGB != targetRGB) { continue; }
//Update the image and check surrounding pixels
image.setRGB(p.x, p.y, replacementRGB);
if (p.x > 0) {
queue.add( new Point(p.x - 1, p.y) );
}
if (p.x +1 < width) {
queue.add( new Point(p.x + 1, p.y) );
}
if (p.y > 0) {
queue.add( new Point(p.x, p.y - 1) );
}
if (p.y +1 < height) {
queue.add( new Point(p.x, p.y + 1) );
}
}
}
}
public static void main(String[] args) throws Exception {
String imageAdress = "https://i.stack.imgur.com/to4SE.png";
BufferedImage image = ImageIO.read(new URL(imageAdress));
FloodFill ff = new FloodFill(image);
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(ff);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
ff.autoFloodFill();
}
}
A slowed down demo:
Run it on line

Here is a flood fill method I found on the web a long time ago:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import java.io.*;
import java.util.LinkedList;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.swing.*;
public class ImageUtil
{
public static void floodFill(BufferedImage image, Point point, Color target, Color replacement)
{
int width = image.getWidth() - 1;
int height = image.getHeight() - 1;
int targetRGB = target.getRGB();
int replacementRGB = replacement.getRGB();
Queue<Point> queue = new LinkedList<Point>();
queue.add( point );
while (!queue.isEmpty())
{
Point p = queue.remove();
int imageRGB = image.getRGB(p.x, p.y);
Color imageColor = new Color(imageRGB);
if (imageRGB != targetRGB) continue;
// Update the image and check surrounding pixels
image.setRGB(p.x, p.y, replacementRGB);
if (p.x > 0) queue.add( new Point(p.x - 1, p.y) );
if (p.x < width) queue.add( new Point(p.x + 1, p.y) );
if (p.y > 0) queue.add( new Point(p.x, p.y - 1) );
if (p.y < height) queue.add( new Point(p.x, p.y + 1) );
}
}
public static void main(String[] args)
throws Exception
{
if (args.length != 1) {
System.err.println("ERROR: Pass filename as argument.");
return;
}
String fileName = args[0];
BufferedImage image = ImageIO.read( new File( fileName ) );
JLabel north = new JLabel( new ImageIcon( fileName ) );
JLabel south = new JLabel( new ImageIcon( image ) );
north.addMouseListener( new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
try
{
BufferedImage image = ImageIO.read( new File( fileName ) );
int rgb = image.getRGB(e.getX(), e.getY());
Color target = new Color( rgb );
floodFill(image, e.getPoint(), target, Color.ORANGE);
south.setIcon( new ImageIcon(image) );
}
catch (Exception e2) {}
}
});
JLabel label = new JLabel("Click on above image for flood fill");
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(north, BorderLayout.NORTH);
frame.add(label);
frame.add(south, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
}
I'll let you decide if it is any more efficient.

You'd typically use a Queue to implement a flood fill, seeded each time you detect a background (in your case white) pixel during a raster scan of the image.
static int[] xd = {-1, 0, 1, 0};
static int[] yd = { 0, 1, 0, -1};
static BufferedImage colorImage(BufferedImage im, Color background, Color[] colors) throws Exception
{
im = ensureImageType(im, BufferedImage.TYPE_INT_ARGB);
int width = im.getWidth();
int height = im.getHeight();
int[] pix = ((DataBufferInt)im.getRaster().getDataBuffer()).getData();
Queue<Integer> q = new LinkedList<>();
for (int i = 0, r = 0; i < width * height; i++)
{
if(pix[i] == background.getRGB())
{
q.add(i);
pix[i] = colors[r++ % colors.length].getRGB();
while(!q.isEmpty())
{
int pos = q.poll();
int x = pos % width;
int y = pos / width;
for(int j = 0; j < 4; j++)
{
int xn = x + xd[j];
if(xn >= 0 && xn < width)
{
int yn = y + yd[j];
if(yn >= 0 && yn < height)
{
int npos = yn * width + xn;
if(pix[npos] == background.getRGB())
{
q.add(npos);
pix[npos] = pix[i];
}
}
}
}
}
}
}
return im;
}
With the helper class:
static BufferedImage ensureImageType(BufferedImage im, int imageType)
{
if (im.getType() != imageType)
{
BufferedImage nim = new BufferedImage(im.getWidth(), im.getHeight(), imageType);
Graphics g = nim.getGraphics();
g.drawImage(im, 0, 0, null);
g.dispose();
im = nim;
}
return im;
}
Test:
Color[] colors = {Color.BLUE, Color.RED, Color.GREEN, Color.ORANGE,
Color.PINK, Color.CYAN, Color.MAGENTA, Color.YELLOW};
BufferedImage im = ImageIO.read(new File("to4SE.png"));
im = colorImage(im, Color.WHITE, colors);
ImageIO.write(im, "png", new File("color.png"));
Output:

Related

Modify color array java

Attatched are 2 methods from code that prints out a gird of 25 boxes in jframe and when you click the space bar the boxes fill up one by one with random colors. I need to modify the code so the first 13 boxes show up in shades of red and the next 12 show up only in shades of blue. Also, I need to change the background color to a random color. Thanks.
public void setColor()
{
int r = (int) (Math.random()*256);
int g = (int) (Math.random()*256);
int b = (int) (Math.random()*256);
myColors[clicked] = new Color(r,g,b);
}
public void paint(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.black);
counter = 0;
for (int x = 0; x < myFloor.length; x++)
{
for (int y = 0; y < myFloor[0].length; y++)
{
if (myColors[counter] != null)
{
for (int i = 0; i < counter+1; i++)
{
setColor();
g2.setColor(myColors[i]);
g2.fill(myFloor[x][y]);
}
}
else
{
g2.setColor(Color.BLACK);
g2.draw(myFloor[x][y]);
}
counter++;
}
}
}
I need to modify the code so the first 13 boxes show up in shades of red and the next 12 show up only in shades of blue. Also, I need to change the background color to a random color. Thanks.
You could...
Use a "color blending" algorithm, which could blend a range of colors together.
The following example basically constructs a color range of dark to light red, light blue to dark blue, split over a normalised range of 0-51% and 52-100%
This has a neat side effect of filling the first 13 squares with shades of red and the last 12 with shades of blue.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int cols = 5;
private int rows = 5;
private int cellSize = 50;
private ColorGradient colorGradient;
public TestPane() {
colorGradient = new ColorGradient(
new float[]{0f, 0.51f, 0.52f, 1f},
new Color[]{Color.RED.darker(), Color.RED.brighter(), Color.BLUE.brighter(), Color.BLUE.darker()}
);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(cols * cellSize, rows * cellSize);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color borderColor = new Color(0, 0, 0, 64);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
float progress = ((row * rows) + col) / (float)(rows * cols);
g2d.setColor(colorGradient.colorAt(progress));
g2d.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
g2d.setColor(borderColor);
g2d.drawRect(col * cellSize, row * cellSize, cellSize, cellSize);
}
}
g2d.dispose();
}
}
public class ColorGradient {
private float[] fractions;
private Color[] colors;
public ColorGradient(float[] fractions, Color[] colors) {
this.fractions = fractions;
this.colors = colors;
}
public Color colorAt(float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(progress);
float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};
float max = range[1] - range[0];
float value = progress - range[0];
float weight = value / max;
color = blend(colorRange[0], colorRange[1], 1f - weight);
} else {
throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
}
} else {
throw new IllegalArgumentException("Colours can't be null");
}
} else {
throw new IllegalArgumentException("Fractions can't be null");
}
return color;
}
protected int[] getFractionIndicies(float progress) {
int[] range = new int[2];
int startPoint = 0;
while (startPoint < fractions.length && fractions[startPoint] <= progress) {
startPoint++;
}
if (startPoint >= fractions.length) {
startPoint = fractions.length - 1;
}
range[0] = startPoint - 1;
range[1] = startPoint;
return range;
}
protected Color blend(Color color1, Color color2, double ratio) {
float r = (float) ratio;
float ir = (float) 1.0 - r;
float rgb1[] = new float[3];
float rgb2[] = new float[3];
color1.getColorComponents(rgb1);
color2.getColorComponents(rgb2);
float red = rgb1[0] * r + rgb2[0] * ir;
float green = rgb1[1] * r + rgb2[1] * ir;
float blue = rgb1[2] * r + rgb2[2] * ir;
if (red < 0) {
red = 0;
} else if (red > 255) {
red = 255;
}
if (green < 0) {
green = 0;
} else if (green > 255) {
green = 255;
}
if (blue < 0) {
blue = 0;
} else if (blue > 255) {
blue = 255;
}
Color color = null;
try {
color = new Color(red, green, blue);
} catch (IllegalArgumentException exp) {
NumberFormat nf = NumberFormat.getNumberInstance();
System.out.println(nf.format(red) + "; " + nf.format(green) + "; " + nf.format(blue));
exp.printStackTrace();
}
return color;
}
}
}
Oh, and if you want to generate a random color, you could simply use something like...
Random rnd = new Random();
Color color = new Color(rnd.nextInt(255), rnd.nextInt(255), rnd.nextInt(255));
You could...
Define the red and blue color bands separately, and then, based on the cell index, decide which band you're going to use.
This provides more "absolute" control over the decision making process. For example, the following index allows the cells between 0-12 inclusively to draw colors from the red band and 13-24 from blue band.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Range {
private int lower;
private int upper;
public Range(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
public int getLower() {
return lower;
}
public int getUpper() {
return upper;
}
public boolean contains(int value) {
return value >= getLower() && value <= getUpper();
}
public int getDistance() {
return getUpper() - getLower();
}
public float normalised(int value) {
return (value - getLower()) / (float)getDistance();
}
}
public class TestPane extends JPanel {
private int cols = 5;
private int rows = 5;
private int cellSize = 50;
private ColorBand redColorBand;
private ColorBand blueColorBand;
private Range redRange = new Range(0, 12);
private Range blueRange = new Range(13, 24);
public TestPane() {
redColorBand = new ColorBand(
new float[]{0f, 1f},
new Color[]{Color.RED.darker(), Color.RED.brighter()}
);
blueColorBand = new ColorBand(
new float[]{0f, 1f},
new Color[]{Color.BLUE.brighter(), Color.BLUE.darker()}
);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(cols * cellSize, rows * cellSize);
}
protected Color colorForSqaure(int index) {
if (redRange.contains(index)) {
return redColorBand.colorAt(redRange.normalised(index));
} else if (blueRange.contains(index)) {
return blueColorBand.colorAt(blueRange.normalised(index));
}
return Color.BLACK;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color borderColor = new Color(0, 0, 0, 64);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
g2d.setColor(colorForSqaure(((row * rows) + col)));
g2d.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
g2d.setColor(borderColor);
g2d.drawRect(col * cellSize, row * cellSize, cellSize, cellSize);
}
}
g2d.dispose();
}
}
public class ColorBand {
private float[] fractions;
private Color[] colors;
public ColorBand(float[] fractions, Color[] colors) {
this.fractions = fractions;
this.colors = colors;
}
public Color colorAt(float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(progress);
float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};
float max = range[1] - range[0];
float value = progress - range[0];
float weight = value / max;
color = blend(colorRange[0], colorRange[1], 1f - weight);
} else {
throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
}
} else {
throw new IllegalArgumentException("Colours can't be null");
}
} else {
throw new IllegalArgumentException("Fractions can't be null");
}
return color;
}
protected int[] getFractionIndicies(float progress) {
int[] range = new int[2];
int startPoint = 0;
while (startPoint < fractions.length && fractions[startPoint] <= progress) {
startPoint++;
}
if (startPoint >= fractions.length) {
startPoint = fractions.length - 1;
}
range[0] = startPoint - 1;
range[1] = startPoint;
return range;
}
protected Color blend(Color color1, Color color2, double ratio) {
float r = (float) ratio;
float ir = (float) 1.0 - r;
float rgb1[] = new float[3];
float rgb2[] = new float[3];
color1.getColorComponents(rgb1);
color2.getColorComponents(rgb2);
float red = rgb1[0] * r + rgb2[0] * ir;
float green = rgb1[1] * r + rgb2[1] * ir;
float blue = rgb1[2] * r + rgb2[2] * ir;
if (red < 0) {
red = 0;
} else if (red > 255) {
red = 255;
}
if (green < 0) {
green = 0;
} else if (green > 255) {
green = 255;
}
if (blue < 0) {
blue = 0;
} else if (blue > 255) {
blue = 255;
}
Color color = null;
try {
color = new Color(red, green, blue);
} catch (IllegalArgumentException exp) {
NumberFormat nf = NumberFormat.getNumberInstance();
System.out.println(nf.format(red) + "; " + nf.format(green) + "; " + nf.format(blue));
exp.printStackTrace();
}
return color;
}
}
}
You could...
Predefine the colors up-front
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private int cols = 5;
private int rows = 5;
private int cellSize = 50;
private Color[] colors = new Color [] {
new Color(64, 0, 0),
new Color(79, 0, 0),
new Color(94, 0, 0),
new Color(109, 0, 0),
new Color(124, 0, 0),
new Color(139, 0, 0),
new Color(154, 0, 0),
new Color(169, 0, 0),
new Color(184, 0, 0),
new Color(199, 0, 0),
new Color(214, 0, 0),
new Color(229, 0, 0),
new Color(244, 0, 0),
new Color(0, 0, 64),
new Color(0, 0, 80),
new Color(0, 0, 96),
new Color(0, 0, 112),
new Color(0, 0, 128),
new Color(0, 0, 144),
new Color(0, 0, 160),
new Color(0, 0, 176),
new Color(0, 0, 192),
new Color(0, 0, 208),
new Color(0, 0, 224),
new Color(0, 0, 240),
};
public TestPane() {
}
#Override
public Dimension getPreferredSize() {
return new Dimension(cols * cellSize, rows * cellSize);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Color borderColor = new Color(0, 0, 0, 64);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
g2d.setColor(colors[((row * rows) + col)]);
g2d.fillRect(col * cellSize, row * cellSize, cellSize, cellSize);
g2d.setColor(borderColor);
g2d.drawRect(col * cellSize, row * cellSize, cellSize, cellSize);
}
}
g2d.dispose();
}
}
}

Gradiently shifting colors in Swing?

So I add 8 TextFields and wanna set their background colors. My idea is to set the first one to red (255, 0, 0) the last one to blue (0, 0, 255) and the 8 (or any number actually) others gradient between these. I'm trying to figure out how to solve it in terms of "If the 'next' variable is 0 increase this variable with same amount as previous variable is decreasing with"
So it could look like in each iteration:
setBackground(255, 0, 0);
setBackground(191, 63, 0);
setBackground(127, 127, 0);
...
setBackground(0, 0, 255);
Now I wanna try and fit this way of increase and decreasing into a for loop that will iterate n times where n is number of TextFields (now 8 for simplicity). Anyone know if there's a clever solution to this?
MRE:
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
public class Apple{
public Apple(int width, int height) {
SwingUtilities.invokeLater(() -> initGUITest(width, height));
}
public void initGUITest(int width, int height) {
JFrame frame = new JFrame();
frame.setSize(width, height);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
JPanel panel = new JPanel();
GridLayout gl = new GridLayout(10, 1);
panel.setLayout(gl);
frame.add(panel);
for(int i = 0; i < 8; i++) {
JTextField jtf = new JTextField("Track " + (i + 1));
jtf.setBackground(new Color(255, 0, 0)); //Start color
panel.add(jtf);
}
}
public static void main(String args[]) {
Apple a = new Apple(300, 300);
}
}
Checking if an value is zero and incrementing or decrementing a value from there is inefficient.
There are 2 ways to go about this
Linear
you calculate an blend value which is (i/stepSize) and use that to linearly interpolate between the start and end value as follows
intermediateColor[0]=(int)(start.getRed()+(end.getRed()-start.getRed())*alpha);
intermediateColor[1]=(int)(start.getGreen()+(end.getGreen()-start.getGreen())*alpha);
intermediateColor[2]=(int)(start.getBlue()+(end.getBlue()-start.getBlue())*alpha);
conversion of blend to float is necessary for interpolation to work here is logic
private static void layout1(JFrame frame)
{
Color
start=Color.RED,
end=Color.BLUE;
int[] intermediateColor=new int[3];
int steps=8;
float alpha;
for(int i=0;i<=steps;i++)
{
JTextField field=new JTextField(10);
alpha=((float)i/steps);
intermediateColor[0]=(int)(start.getRed()+(end.getRed()-start.getRed())*alpha);
intermediateColor[1]=(int)(start.getGreen()+(end.getGreen()-start.getGreen())*alpha);
intermediateColor[2]=(int)(start.getBlue()+(end.getBlue()-start.getBlue())*alpha);
field.setBackground(new Color(intermediateColor[0],intermediateColor[1],intermediateColor[2]));
frame.add(field);
}
}
Output :
KeyFrames
An more complicated example involves using key frames where you dynamically change the start and end points based on your i value
Here are the keyframes
int[] checkPoints={0,2,4,6,8};
Color[] colors={Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW,Color.MAGENTA};
what this means is that for
checkboxes 0->2 interpolate between RED & GREEN
checkboxes 3->4 interpolate between GREEN & BLUE
checkboxes 5->6 interpolate between BLUE & YELLOW
checkboxes 7->8 interpolate between YELLOW & MAGENTA
The logic lies in this code
//loop through all checkpoints
for(int j=0;j<checkPoints.length-1;j++)
{
//check if i lies in between these 2 checkpoints
if(i>=checkPoints[j] && i<=checkPoints[j+1])
{
//interpolate between this & the next checkpoint
checkPoint=j;
start=colors[checkPoint];
end=colors[checkPoint+1];
//distance of i from start checkpoint/ total distance between checkpoints
alpha=(float)(i-checkPoints[checkPoint])/(checkPoints[checkPoint+1]-checkPoints[checkPoint]);
}
}
Here is the full code
public class Test
{
public static void main(String[] args)
{
JFrame frame=new JFrame("TEST");
frame.setContentPane(new JPanel(new FlowLayout(FlowLayout.LEADING,10,0)));
layout2(frame);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private static void layout1(JFrame frame)
{
Color
start=Color.RED,
end=Color.BLUE;
int[] intermediateColor=new int[3];
int steps=8;
float alpha;
for(int i=0;i<=steps;i++)
{
JTextField field=new JTextField(10);
alpha=((float)i/steps);
intermediateColor[0]=(int)(start.getRed()+(end.getRed()-start.getRed())*alpha);
intermediateColor[1]=(int)(start.getGreen()+(end.getGreen()-start.getGreen())*alpha);
intermediateColor[2]=(int)(start.getBlue()+(end.getBlue()-start.getBlue())*alpha);
field.setBackground(new Color(intermediateColor[0],intermediateColor[1],intermediateColor[2]));
frame.add(field);
}
}
private static void layout2(JFrame frame)
{
int[] checkPoints={0,2,4,6,8};
Color[] colors={Color.RED,Color.GREEN,Color.BLUE,Color.YELLOW,Color.MAGENTA};
int[] intermediateColor=new int[3];
int steps=8;
int checkPoint;
float alpha=0;
Color start=null,end=null;
for(int i=0;i<=steps;i++)
{
JTextField field=new JTextField(10);
for(int j=0;j<checkPoints.length-1;j++)
{
if(i>=checkPoints[j] && i<=checkPoints[j+1])
{
checkPoint=j;
start=colors[checkPoint];
end=colors[checkPoint+1];
alpha=(float)(i-checkPoints[checkPoint])/(checkPoints[checkPoint+1]-checkPoints[checkPoint]);
}
}
intermediateColor[0]=(int)(start.getRed()+(end.getRed()-start.getRed())*alpha);
intermediateColor[1]=(int)(start.getGreen()+(end.getGreen()-start.getGreen())*alpha);
intermediateColor[2]=(int)(start.getBlue()+(end.getBlue()-start.getBlue())*alpha);
field.setBackground(new Color(intermediateColor[0],intermediateColor[1],intermediateColor[2]));
frame.add(field);
}
}
}
Output :
So, any number of ways you might do this, but for me, personally, I'd look towards using some kind of "blending" algorithm which would allow you to establish the "range" of colors you want and then based on some value (ie a index or percentage), generate a color which is blend of those colors (within the range).
For example...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridwidth = GridBagConstraints.REMAINDER;
ColorBlender blender = new ColorBlender(new float[] {0, 1}, new Color[] {Color.RED, Color.BLUE});
for (int index = 0; index < 8; index++) {
Color color = blender.blendedColorAt(index / 7f);
System.out.println(color);
JTextField textField = new JTextField(10);
textField.setBackground(color);
add(textField, gbc);
}
}
}
public class ColorBlender {
private float[] fractions;
private Color[] colors;
public ColorBlender(float[] fractions, Color[] colors) {
this.fractions = fractions;
this.colors = colors;
}
public Color blendedColorAt(float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(fractions, progress);
float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};
float max = range[1] - range[0];
float value = progress - range[0];
float weight = value / max;
color = blend(colorRange[0], colorRange[1], 1f - weight);
} else {
throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
}
} else {
throw new IllegalArgumentException("Colours can't be null");
}
} else {
throw new IllegalArgumentException("Fractions can't be null");
}
return color;
}
protected int[] getFractionIndicies(float[] fractions, float progress) {
int[] range = new int[2];
int startPoint = 0;
while (startPoint < fractions.length && fractions[startPoint] <= progress) {
startPoint++;
}
if (startPoint >= fractions.length) {
startPoint = fractions.length - 1;
}
range[0] = startPoint - 1;
range[1] = startPoint;
return range;
}
protected Color blend(Color color1, Color color2, double ratio) {
float r = (float) ratio;
float ir = (float) 1.0 - r;
float rgb1[] = new float[3];
float rgb2[] = new float[3];
color1.getColorComponents(rgb1);
color2.getColorComponents(rgb2);
float red = rgb1[0] * r + rgb2[0] * ir;
float green = rgb1[1] * r + rgb2[1] * ir;
float blue = rgb1[2] * r + rgb2[2] * ir;
if (red < 0) {
red = 0;
} else if (red > 255) {
red = 255;
}
if (green < 0) {
green = 0;
} else if (green > 255) {
green = 255;
}
if (blue < 0) {
blue = 0;
} else if (blue > 255) {
blue = 255;
}
Color color = null;
try {
color = new Color(red, green, blue);
} catch (IllegalArgumentException exp) {
NumberFormat nf = NumberFormat.getNumberInstance();
System.out.println(nf.format(red) + "; " + nf.format(green) + "; " + nf.format(blue));
exp.printStackTrace();
}
return color;
}
}
}
Okay, but I want to blend between three colors
Okay, not an issue. Simply add another "stop" and the color for that stop, for example...
ColorBlender blender = new ColorBlender(new float[] {0f, 0.5f, 1f}, new Color[] {Color.RED, Color.YELLOW, Color.BLUE});
will produce...
Want to add more fields? No worries, just change Color color = blender.blendedColorAt(index / 7f); to so that 7f becomes the number of expected fields - 1 (remember, we're starting the index at 0 😉)
Here is a class that can generate a series of colors that can transition from one color to another for a given number of steps.
Simple usage would be:
ColorTransition ct = new ColorTransition(Color.RED, Color.BLUE, 8);
If you need multiple transitions you could do:
ColorTransition ct = new ColorTransition(Color.RED, Color.BLUE, 8);
ct.transitionTo(Color.GREEN, 4);
which would then transition from BLUE to GREEN.
Once all the transition colors are generated you can access them separately:
import java.awt.*;
import javax.swing.*;
import java.util.*;
public class ColorTransition
{
private ArrayList<Color> colors;
public ColorTransition(Color startColor, Color endColor, int steps)
{
colors = new ArrayList<>(steps);
colors.add( startColor );
transitionTo(endColor, steps);
}
public void transitionTo(Color endColor, int steps)
{
Color startColor = colors.get(colors.size() - 1);
float rDelta = endColor.getRed() - startColor.getRed();
float gDelta = endColor.getGreen() - startColor.getGreen();
float bDelta = endColor.getBlue() - startColor.getBlue();
for (int i = 1; i < steps; i++)
{
float stepIncrement = (float)i / (steps - 1);
int rValue = (int)(startColor.getRed() + (rDelta * stepIncrement));
int gValue = (int)(startColor.getGreen() + (gDelta * stepIncrement));
int bValue = (int)(startColor.getBlue() + (bDelta * stepIncrement));
Color color = new Color(rValue, gValue, bValue);
colors.add( color );
}
}
public int size()
{
return colors.size();
}
public Color getColorAt(int index)
{
return colors.get( index );
}
private static void createAndShowGUI()
{
ColorTransition ct = new ColorTransition(Color.RED, Color.BLUE, 8);
// ct.transitionTo(Color.GREEN, 4);
JPanel panel = new JPanel( new GridLayout(0, 1) );
for (int i = 0; i < ct.size(); i++)
{
JTextField textField = new JTextField(25);
textField.setBackground( ct.getColorAt(i) );
panel.add( textField );
}
JFrame frame = new JFrame("Color Transition");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
java.awt.EventQueue.invokeLater( () -> createAndShowGUI() );
}
}

How to repaint image for every variable change from JSlider?

I want to make the displayed image repainted for everytime i change the slider position.
I've already made the Every change of the Variable from JSlider is added to the pixel. But i just don't know how to repaint it.
package training;
import javax.swing.*;
import java.io.*;
import java.awt.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.swing.plaf.SliderUI;
import java.awt.Color;
import java.util.Arrays;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Training extends JPanel {
public BufferedImage image;
double maxw, maxh;
double w, h, ratio;
int warna, red, green, blue, abu, value;
int forT1, forT2;
int[][] bmpR;
int[][] bmpG;
int[][] bmpB;
int[][] alpha;
public Training () {
super();
try {
image = ImageIO.read(new File("src/training/V.jpg"));
}
catch (IOException e) {
//Not handled.
}
maxw = 750;
maxh = 600;
w = image.getWidth();
h = image.getHeight();
bmpR = new int[(int)w][(int)h];
bmpG = new int[(int)w][(int)h];
bmpB = new int[(int)w][(int)h];
if (w > h) {
if (w > maxw) {
ratio = maxw / w;
h = h * ratio; // Reset height to match scaled image
w = w * ratio;
}
}
if (w <= h) {
if (h > maxh) {
ratio = maxh / h;
w = w * ratio; // Reset height to match scaled image
h = h * ratio;
}
}
try {
for( int i = 0; i < w; i++ ) {
for( int j = 0; j < h; j++ ) {
Color c = new Color(image.getRGB(i, j));
bmpR [i][j] = c.getRed();
bmpG [i][j] = c.getGreen();
bmpB [i][j] = c.getBlue();
// alpha = c.getAlpha();
}
}
System.out.println(bmpB[40][40]);
}
catch (Exception e) {
System.out.println("Terjadi kesalahan saat mengambil data pixel");
e.printStackTrace();
return;
}
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Image i = image.getScaledInstance((int)w, (int)h,Image.SCALE_SMOOTH);
g.drawImage(i, 20, 20, null);
}
public static void main(String[] args) {
final Training ns = new Training();
System.out.println("User dir: " + System.getProperty("user.dir"));
JFrame f = new JFrame("Window");
JPanel p = new Training();
f.setSize(1100, 600);
p.setSize(750, 600);
f.add(p);
JSlider Temp = new JSlider(-50, 50, 0);
Temp.setMajorTickSpacing(10);
Temp.setMinorTickSpacing(1);
Temp.addChangeListener(new ChangeListener () {
public void stateChanged(ChangeEvent evt) {
JSlider Temp = (JSlider) evt.getSource();
if (Temp.getValueIsAdjusting()) {
ns.value = Temp.getValue();
for(ns.forT1 = 0; ns.forT1 < ns.w; ns.forT1++ ) {
for(ns.forT2 = 0; ns.forT2 < ns.h; ns.forT2++ ) {
ns.bmpB[ns.forT1][ns.forT2] = ns.bmpB[ns.forT1][ns.forT2] - ns.value;
if (ns.bmpB[ns.forT1][ns.forT2] > 255) {
ns.bmpB[ns.forT1][ns.forT2] = 255;
}
if (ns.bmpB[ns.forT1][ns.forT2] < 0) {
ns.bmpB[ns.forT1][ns.forT2] = 0;
}
ns.bmpR[ns.forT1][ns.forT2] = ns.bmpR[ns.forT1][ns.forT2] + ns.value;
if (ns.bmpR[ns.forT1][ns.forT2] > 255) {
ns.bmpR[ns.forT1][ns.forT2] = 255;
}
if (ns.bmpR[ns.forT1][ns.forT2] < 0) {
ns.bmpR[ns.forT1][ns.forT2] = 0;
}
}
}
}
ns.repaint();
}
});
f.add(Temp, BorderLayout.EAST);
f.setVisible(true);
}
}
Did i misplaced the ChangeListener or should i put paintComponent method after the change listener happens?
The random indentation of your code is making it hard to figure out exactly what's going on, but I don't see repaint() anywhere inside your ChangeListener. You need to use repaint() to trigger the repainting of your component.

Why won't gc.clearRect clear the canvas?

I have some code in a MazeUI class that creates a couple of fields in JavaFX for entering the height and width of a Maze to be generated along with a Button and the event for the button. On clicking Generate Maze the code should generate a random maze.
Now this works fine. But the line to clear the canvas after the Maze is first drawn using gc.clearRect(x, y, z, a) doesn't appear to work and Maze's are re-drawn on top of one another on subsequent clicks:
package dojo.maze.ui;
import dojo.maze.generator.MazeGenerator;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.effect.BoxBlur;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.StrokeLineCap;
import javafx.scene.shape.StrokeLineJoin;
import javafx.stage.Stage;
public class MazeUI extends Application {
private MazeGenerator generator;
private Integer height = 15;
private Integer width = 15;
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Dojo: Solving a Maze");
Group root = new Group();
drawMazeView(root);
primaryStage.setScene(new Scene(root, Color.WHITE));
primaryStage.show();
}
private GraphicsContext initialiseGraphicsContext(Canvas canvas) {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.BLACK);
gc.fillRect(0,0,600,600);
gc.setStroke(Color.GREEN);
gc.setLineCap( StrokeLineCap.ROUND );
gc.setLineJoin( StrokeLineJoin.ROUND );
gc.setLineWidth(3);
BoxBlur blur = new BoxBlur();
blur.setWidth(1);
blur.setHeight(1);
blur.setIterations(1);
gc.setEffect(blur);
return gc;
}
private void drawMazeView(Group root) {
Canvas canvas = new Canvas(800, 800);
GraphicsContext gc = initialiseGraphicsContext(canvas);
GridPane.setConstraints(canvas, 0, 6);
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10, 10));
grid.setVgap(5);
grid.setHgap(5);
Label heightLabel = new Label("Height:");
final TextField heightTextField = new TextField();
HBox hbHeight = new HBox();
hbHeight.getChildren().addAll(heightLabel, heightTextField);
hbHeight.setSpacing(10);
GridPane.setConstraints(hbHeight, 0, 0);
Label widthLabel = new Label("Label:");
final TextField widthTextField = new TextField();
HBox hbWidth = new HBox();
hbWidth.getChildren().addAll(widthLabel, widthTextField);
hbWidth.setSpacing(10);
GridPane.setConstraints(hbWidth, 0, 2);
VBox fieldsBox = new VBox();
fieldsBox.getChildren().addAll(hbHeight, hbWidth);
// Create button that allows you to generate a new maze
Button btn = new Button();
btn.setText("Generate Random Maze");
btn.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
System.out.println("Button clicked!");
height = Integer.valueOf(heightTextField.getText());
width = Integer.valueOf(widthTextField.getText());
// clear old maze
gc.clearRect(0, 0,
canvas.getHeight(),
canvas.getWidth());
generator = new MazeGenerator(height, width);
drawMaze(gc);
}
});
GridPane.setConstraints(btn, 0, 4);
grid.getChildren().addAll(fieldsBox, btn, canvas);
root.getChildren().add(grid);
}
void drawMaze(GraphicsContext gc) {
int[][] maze = generator.maze();
int xPos = 20,
yPos = 20,
length = 40,
gap = 10;
for (int i = 0; i < width; i++) {
xPos = 20;
// draw the north edge
for (int j = 0; j < height; j++) {
if ((maze[j][i] & 1) == 0) {
gc.strokeLine(xPos, yPos, xPos + length, yPos); // horizontal
}
xPos += length + gap;
System.out.print((maze[j][i] & 1) == 0 ? "+---" : "+ ");
}
System.out.println("+");
xPos = 20;
// draw the west edge
for (int j = 0; j < height; j++) {
if ((maze[j][i] & 8) == 0) {
gc.strokeLine(xPos, yPos, xPos, yPos + length); // vertical
}
xPos += length + gap;
System.out.print((maze[j][i] & 8) == 0 ? "| " : " ");
}
gc.strokeLine(xPos, yPos, xPos, yPos + length); // vertical
System.out.println("|");
yPos += length + gap;
}
// draw the bottom line
xPos = 20; // reset x pos to western edge
for (int j = 0; j < height; j++) {
gc.strokeLine(xPos, yPos, xPos + length, yPos); // horizontal
System.out.print("+---");
xPos += length + gap;
}
System.out.println("+");
}
public static void main(String[] args) {
launch(args);
}
}
Code for MazeGenerator:
package dojo.maze.generator;
import java.util.Arrays;
import java.util.Collections;
/*
* recursive backtracking algorithm
* shamelessly borrowed from the ruby at
* http://weblog.jamisbuck.org/2010/12/27/maze-generation-recursive-backtracking
*/
public class MazeGenerator {
private final int x;
private final int y;
private final int[][] maze;
public static final int[][] mazeProblemOne = new int[][] {{2,5,2,3,7,3,1,6,7,3,3,5,6,3,5},{4,10,3,5,12,6,3,9,10,3,5,12,8,6,13},{12,6,5,12,12,14,3,1,6,3,9,10,3,9,12},{12,12,12,12,12,12,6,3,9,4,6,7,1,6,9},{14,9,10,9,12,12,12,6,5,12,12,10,5,12,4},{12,2,7,5,12,12,10,9,12,10,13,4,10,9,12},{12,6,9,12,12,12,2,5,10,5,10,11,3,3,13},{12,10,5,12,10,11,3,13,4,10,5,6,3,1,12},{12,6,9,10,5,4,6,9,12,6,9,12,6,3,9},{10,9,6,1,12,10,11,3,9,12,6,13,12,2,5},{6,7,9,6,9,6,3,3,3,9,8,12,12,6,13},{8,12,6,9,6,13,6,3,7,3,3,13,12,12,12},{6,9,10,5,12,12,12,4,10,3,5,8,12,12,8},{14,3,5,12,8,12,10,11,3,5,10,5,12,10,5},{10,1,10,11,3,9,2,3,3,11,1,10,11,3,9}};
public static final int[][] mazeProblemTwo = new int[][] {{2,5,6,5,6,3,1,6,3,3,7,5,2,3,5},{4,10,9,12,14,3,3,9,6,5,12,10,3,3,9},{14,3,3,9,8,6,3,5,12,12,10,3,3,3,5},{12,6,3,3,5,12,4,10,9,10,3,3,3,3,13},{12,12,6,5,12,12,10,3,3,3,5,6,3,3,9},{12,10,9,12,10,9,6,5,6,3,13,10,3,3,5},{10,3,5,12,6,5,12,10,9,4,14,3,3,1,12},{4,6,9,12,12,10,9,6,3,11,13,6,3,3,9},{12,12,2,13,14,3,5,8,6,5,8,10,3,3,5},{12,10,3,9,12,4,12,6,9,12,6,5,6,3,9},{14,7,3,1,10,13,12,12,4,12,12,10,9,6,1},{12,10,3,7,1,12,10,11,9,12,12,2,3,11,5},{12,2,5,12,6,9,2,5,6,9,10,3,3,5,12},{10,5,12,12,12,6,3,13,12,6,7,1,6,9,12},{2,11,9,10,11,9,2,9,10,9,10,3,11,3,9}};
public static final int[][] mazeProblemThree = new int[][] {{2,5,2,3,7,3,3,3,5,6,3,1,6,7,1},{4,10,3,5,12,2,5,6,9,10,3,3,9,14,5},{10,7,5,12,10,5,14,9,6,5,6,3,5,12,12},{6,9,8,10,3,9,12,6,13,12,10,5,12,12,12},{12,6,3,7,1,6,9,12,12,10,3,9,12,8,12},{12,12,4,10,5,10,5,8,14,3,5,2,11,3,13},{12,10,13,4,10,5,10,5,10,5,10,5,6,5,12},{14,1,12,10,5,14,1,12,6,9,4,10,9,12,12},{14,5,10,5,12,10,5,12,12,2,15,3,1,12,12},{8,12,6,9,10,5,12,12,10,3,9,6,3,9,12},{6,9,14,3,3,9,12,10,3,3,3,9,4,6,9},{10,5,12,6,3,3,13,6,3,3,1,6,11,9,4},{6,9,12,10,5,6,11,9,6,3,3,9,6,3,13},{14,5,12,4,12,10,1,6,9,6,5,2,13,4,12},{8,10,11,9,10,3,3,11,3,9,10,3,9,10,9}};
public static final int[][] mazeProblemFour = new int[][] {{2,3,3,5,4,6,7,3,3,5,6,5,6,3,5},{6,3,1,12,12,12,10,3,5,10,9,12,10,5,12},{12,6,3,9,14,9,4,6,9,2,3,11,1,12,12},{14,9,2,3,11,5,12,10,3,5,6,3,3,9,12},{10,5,6,5,2,11,11,5,4,12,12,6,7,1,12},{4,10,9,10,3,5,6,9,12,10,9,8,12,6,13},{14,5,4,6,3,9,12,6,11,5,6,3,9,12,8},{12,10,9,12,4,6,9,10,5,12,14,3,5,10,5},{12,6,5,12,12,12,6,3,9,12,8,6,13,4,12},{14,9,10,9,12,12,10,3,5,10,5,12,10,13,12},{12,2,7,5,10,11,3,3,9,6,9,12,2,9,12},{10,3,9,10,3,3,5,4,6,11,3,9,6,3,13},{6,5,6,7,3,5,12,10,9,6,3,3,9,6,9},{12,10,9,10,5,8,12,6,5,10,5,6,5,10,5},{10,3,3,1,10,3,11,9,10,3,9,8,10,3,9}};
public static final int[][] mazeProblemFive = new int[][] {{2,3,5,6,3,3,7,5,6,3,3,3,7,3,5},{6,5,12,12,6,3,9,8,10,5,4,6,9,4,12},{12,12,12,12,10,3,5,6,5,10,13,10,5,14,9},{12,10,9,10,5,4,10,9,14,1,12,4,12,10,5},{10,5,6,5,10,15,3,1,14,5,14,9,10,5,8},{6,9,12,10,5,8,6,5,8,10,9,6,5,14,5},{10,3,9,4,12,6,9,10,3,3,3,9,12,8,12},{6,3,7,13,12,10,7,5,2,7,7,1,10,3,13},{14,1,12,8,10,5,12,12,6,9,12,6,3,3,9},{8,6,13,6,3,9,12,12,12,2,13,12,6,3,5},{6,9,8,12,4,6,9,12,14,5,8,10,9,4,12},{12,6,3,9,10,9,6,9,8,10,7,5,6,11,9},{12,10,3,3,3,5,14,3,3,5,12,12,10,5,4},{14,1,6,5,6,9,10,3,1,12,12,10,1,10,13},{10,3,9,10,11,3,3,3,3,9,10,3,3,3,9}};
public MazeGenerator(int x, int y) {
this.x = x;
this.y = y;
maze = new int[this.x][this.y];
generateMaze(0, 0);
}
public void display() {
for (int i = 0; i < y; i++) {
// draw the bbc.north edge
for (int j = 0; j < x; j++) {
System.out.print((maze[j][i] & 1) == 0 ? "+---" : "+ ");
}
System.out.println("+");
// draw the west edge
for (int j = 0; j < x; j++) {
System.out.print((maze[j][i] & 8) == 0 ? "| " : " ");
}
System.out.println("|");
}
// draw the bottom line
for (int j = 0; j < x; j++) {
System.out.print("+---");
}
System.out.println("+");
}
private void generateMaze(int cx, int cy) {
DIR[] dirs = DIR.values();
Collections.shuffle(Arrays.asList(dirs));
for (DIR dir : dirs) {
int nx = cx + dir.dx;
int ny = cy + dir.dy;
if (between(nx, x) && between(ny, y)
&& (maze[nx][ny] == 0)) {
maze[cx][cy] |= dir.bit;
maze[nx][ny] |= dir.opposite.bit;
generateMaze(nx, ny);
}
}
}
private static boolean between(int v, int upper) {
return (v >= 0) && (v < upper);
}
public int[][] maze() {
return maze;
}
private enum DIR {
N(1, 0, -1), S(2, 0, 1), E(4, 1, 0), W(8, -1, 0);
private final int bit;
private final int dx;
private final int dy;
private DIR opposite;
// use the static initializer to resolve forward references
static {
N.opposite = S;
S.opposite = N;
E.opposite = W;
W.opposite = E;
}
private DIR(int bit, int dx, int dy) {
this.bit = bit;
this.dx = dx;
this.dy = dy;
}
};
public static void main(String[] args) {
int x = args.length >= 1 ? (Integer.parseInt(args[0])) : 8;
int y = args.length == 2 ? (Integer.parseInt(args[1])) : 8;
MazeGenerator maze = new MazeGenerator(x, y);
maze.display();
}
}
How do I fix the code so that it correctly clears the Maze after each button click? Am I missing something?
Remove the BoxBlur effect before you clear the canvas, then re-apply it. Clearing the canvas simply paints with a transparent color. So the BoxBlur effect will affect that too.
And what jewelsea said. You'd have found it yourself if you'd only reduced the code to a minimum ;-)
For people looking for an answer, another reason why it may not be working is if you set the GraphicsContext.globalBlendMode to something like BlendMode.SCREEN.
Since clearRect() paints the canvas with transparent pixels, a transparent pixel would have no effect in that BlendMode.
Set the gc.globalBlendMode = BlendMode.SRC_OVER right before calling clearRect()

Java 2D Scrolling - background not displaying

I am trying to make a scrolling game - where the player (in space) is constantly at the center of the screen. As he moves left right up and down, a background spritesheet will randomly generate coloured stars - so the moving stars will be an indication of which direction the player is moving in.
The problem I am now having is that the stars are not displaying when I run the game. Each tile is supposed to be 32x32, each containing at least one star, with the 'nostars' tile being empty. When I run the game, I just get a black screen.
RandomLevel.java:
protected void generateLevel() {
for(int y = 0; y < height; y++) {
for(int x = 0; x < width; x++) {
bgtiles[x + y * width] = random.nextInt(4);
}
}
}
Level.java
public void render(int xScroll, int yScroll, Screen screen) {
screen.setOffset(xScroll, yScroll);
int x0 = xScroll >> 5;
int x1 = (xScroll + screen.width + 32) >> 5;
int y0 = yScroll >> 5;
int y1 = (yScroll + screen.height + 32) >> 5;
for(int y = y0; y < y1; y++) {
for(int x = x0; x < x1; x++) {
getTile(x, y).render(x, y, screen);
}
}
}
public Tile getTile(int x, int y) {
if(x < 0 || y < 0 || x >= width || y >= height) return Tile.nostars;
if(bgtiles[x + y * width] == 0) return Tile.stars1;
if(bgtiles[x + y * width] == 1) return Tile.stars2;
if(bgtiles[x + y * width] == 2) return Tile.stars3;
if(bgtiles[x + y * width] == 3) return Tile.stars4;
else return Tile.nostars;
}
SpaceTile.java
public class SpaceTile extends Tile {
public SpaceTile(Sprite sprite) {
super(sprite);
}
public void render(int x, int y, Screen screen) {
screen.renderTile(x << 5, y << 5, this);
}
}
SpriteSheet.java
public static SpriteSheet bgtiles = new SpriteSheet("/textures/bgsheet.png", 256);
Sprite.java
public static Sprite spaceSprite = new Sprite(32, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars1 = new Sprite(64, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars2 = new Sprite(96, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars3 = new Sprite(128, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars4 = new Sprite(160, 0, 0, SpriteSheet.bgtiles);
Tile.java
public class Tile {
public int x, y;
public Sprite sprite;
public static Tile nostars = new SpaceTile(Sprite.spaceSprite);
public static Tile stars1 = new SpaceTile(Sprite.stars1);
public static Tile stars2 = new SpaceTile(Sprite.stars2);
public static Tile stars3 = new SpaceTile(Sprite.stars3);
public static Tile stars4 = new SpaceTile(Sprite.stars4);
public Tile(Sprite sprite) {
this.sprite = sprite;
}
public void render(int x, int y, Screen screen) {
}
public boolean solid() {
return false;
}
}
Game.java
public class Game extends Canvas implements Runnable {
private static final long serialVersionUID = 1L;
public static int width = 300;
public static int height = width / 16 * 9;
public static int scale = 3;
public static String title = "Game";
private Thread thread;
private JFrame frame;
private Keyboard key;
private Level level;
private boolean running = false;
private Screen screen;
private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
public Game() {
Dimension size = new Dimension(width * scale, height * scale);
setPreferredSize(size);
screen = new Screen(width, height);
frame = new JFrame();
key = new Keyboard();
level = new RandomLevel(64, 64);
addKeyListener(key);
}
public synchronized void start() {
running = true;
thread = new Thread(this, "Display");
thread.start();
}
public synchronized void stop() {
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void run() {
double ns = 1000000000.0 / 60.0;
double delta = 0;
int frames = 0;
int updates = 0;
long lastTime = System.nanoTime();
long timer = System.currentTimeMillis();
requestFocus();
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while(delta >= 1) {
update();
updates++;
delta--;
}
render();
frames++;
if(System.currentTimeMillis() - timer >= 1000) {
timer += 1000;
frame.setTitle(title + " | " + updates + " ups, " + frames + " fps");
frames = 0;
updates = 0;
}
}
stop();
}
int x, y = 0;
public void update() {
key.update();
if(key.up == true) y--;
if(key.down == true) y++;
if(key.left == true) x--;
if(key.right == true) x++;
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if (bs == null) {
createBufferStrategy(3);
return;
}
screen.clear();
level.render(x, y, screen);
for(int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
}
Graphics g = bs.getDrawGraphics();
g.drawImage(image, 0, 0, getWidth(), getHeight(), null);
g.dispose();
bs.show();
}
public static void main(String[] args) {
Game game = new Game();
game.frame.setResizable(false);
game.frame.setTitle(Game.title);
game.frame.add(game);
game.frame.pack();
game.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.frame.setLocationRelativeTo(null);
game.frame.setVisible(true);
game.start();
}
}
bgsheet.png
https://i.imgur.com/0yUKql2.png?1
In the generatelevel() I am only trying it out with the first 4 tiles, not all of the 64 tiles.
When I run the game, I expect to see 4 different stars scattered everywhere but instead I just get a black screen.
Thanks in advance for any help !
From the code posted, it appears that you forgot to load the background into image. I placed this code into a new public method called loadAssets(). Call this before you call game.start().
public void loadAssets() {
try {
image = ImageIO.read(new URL("https://i.imgur.com/0yUKql2.png?1"));
} catch (MalformedURLException ex) {
Logger.getLogger(GameTwo.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(GameTwo.class.getName()).log(Level.SEVERE, null, ex);
}
}
I also commented out the following code in render().
screen.clear();
level.render(x, y, screen);
for(int i = 0; i < pixels.length; i++) {
pixels[i] = screen.pixels[i];
}
So from what I understand, you make a call to draw using BufferedImage image, however you have not actually loaded your image data into the variable image. I will provide a code snippet that you may need to tailor a bit
File imageFile = new File("/path/to/image.file");
BufferedImage image = ImageIO.read(imageFile);
There may be a faster way as you have already called
private BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
But as you can probably imagine, that doesn't actually connect you image variable to an image file. My best guess is that the code I provided will work, but honestly you'll have to try it.
Happy coding and leave a comment if you have any questions!
The problem turned out to be simply that I had the wrong co-ordinates for each sprite. Sorry for wasting your time and thanks for the help anyway!
public static Sprite spaceSprite = new Sprite(32, 0, 0, SpriteSheet.bgtiles);
public static Sprite stars1 = new Sprite(32, 1, 0, SpriteSheet.bgtiles);
public static Sprite stars2 = new Sprite(32, 2, 0, SpriteSheet.bgtiles);
public static Sprite stars3 = new Sprite(32, 3, 0, SpriteSheet.bgtiles);
public static Sprite stars4 = new Sprite(32, 4, 0, SpriteSheet.bgtiles);

Categories