I have a strange problem, I can figure it out.
here is my program running:
the clock on the right shouldn't even be there, it should just be string that displays the time. I create two different objects and add them to a content panel, then add the content panel to the JFrame. I also have a menu bar that I add to the JFrame and it shows up on the right side of the content pane. Here is my main class:
class DigitalClock3DialsView extends DigitalClockView {
private static int caps[] = { BasicStroke.CAP_BUTT,
BasicStroke.CAP_SQUARE, BasicStroke.CAP_ROUND};
private static int joins[] = { BasicStroke.JOIN_MITER,
BasicStroke.JOIN_BEVEL, BasicStroke.JOIN_ROUND};
private static Color colors[] = {Color.gray, Color.pink, Color.lightGray};
private static BasicStroke bs1 = new BasicStroke(1.0f);
// three arms of clock
private Line2D lines[] = new Line2D[3];
private int rAmt[] = new int[lines.length];
private int speed[] = new int[lines.length];
private BasicStroke strokes[] = new BasicStroke[lines.length];
private GeneralPath path;
private Point2D[] pts;
private float size;
private Ellipse2D ellipse = new Ellipse2D.Double();
private BufferedImage bimg;
//variables to keep track if minutes or hours increased
private int oldMinute;
private int oldHour;
/**
* init background
*/
public void init() {
setBackground(Color.white);
}
/**
* reset view: the shape of arms, etc
*/
public void reset(int w, int h)
{
oldMinute = 0;
oldHour = 0;
size = (w > h) ? h/6f : w/6f;
for (int i = 0; i < lines.length; i++) {
lines[i] = new Line2D.Float(0,0,size,0);
strokes[i] = new BasicStroke(size/3, caps[i], joins[i]);
rAmt[i] = 270; // vertical
}
//speed of the 3 arms
speed[0] = 6;
speed[1] = 6;
speed[2] = 6;
path = new GeneralPath();
path.moveTo(size, -size/2);
path.lineTo(size+size/2, 0);
path.lineTo(size, +size/2);
// YW: do not know how to show the regular clock view
// with inc of 5 mins on the contour
// can you help to fix this?
ellipse.setFrame(w/2-size*2-4.5f,h/2-size*2-4.5f,size*4,size*4);
double linApproxLen = 0.75 * size * 0.258819; // sin(15 degree)
PathIterator pi = ellipse.getPathIterator(null, linApproxLen);
Point2D[] points = new Point2D[100];
int num_pts = 0;
while ( !pi.isDone() )
{
float[] pt = new float[6];
switch ( pi.currentSegment(pt) ) {
case FlatteningPathIterator.SEG_MOVETO:
case FlatteningPathIterator.SEG_LINETO:
points[num_pts] = new Point2D.Float(pt[0], pt[1]);
num_pts++;
}
pi.next();
}
pts = new Point2D[num_pts];
System.arraycopy(points, 0, pts, 0, num_pts);
}
private Point2D[] computePoints(double w, double h, int n)
{
Point2D points[] = new Point2D[n];
double angleDeltaRad = Math.PI * 2 / n;
for (int i=0; i<n; i++)
{
double angleRad = i * angleDeltaRad;
double ca = Math.cos(angleRad);
double sa = Math.sin(angleRad);
double x = sa * w/2;
double y = ca * h/2;
points[i] = new Point2D.Double(x,y);
}
return points;
}
public void paint(Graphics g)
{
System.out.printf("seconds: %s, minutes: %d \n", second, minute);
Dimension d = getSize();
updateSecond(d.width, d.height);
if(oldMinute < minute || (oldMinute==59 && minute==0))
{
oldMinute = minute;
updateMinute(d.width, d.height);
}
if(oldHour < hour || (oldHour==12 && hour == 1) )
{
oldHour = hour;
updateHour(d.width, d.height);
}
Graphics2D g2 = createGraphics2D(d.width, d.height);
drawClockArms(d.width, d.height, g2);
g2.dispose();
g.drawImage(bimg, 0, 0, this);
}
public Graphics2D createGraphics2D(int w, int h) {
// standard Java 2D code
Graphics2D g2 = null;
if (bimg == null || bimg.getWidth() != w || bimg.getHeight() != h) {
bimg = (BufferedImage) createImage(w, h);
reset(w, h);
}
g2 = bimg.createGraphics();
g2.setBackground(getBackground());
g2.clearRect(0, 0, w, h);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
return g2;
}
private void drawClockArms(int w, int h, Graphics2D g2) {
ellipse.setFrame(w/2-size,h/2-size,size*2,size*2);
g2.setColor(Color.black);
g2.draw(ellipse);
for (int i = 0; i < lines.length; i++) {
AffineTransform at = AffineTransform.getTranslateInstance(w/2,h/2);
at.rotate(Math.toRadians(rAmt[i]));
g2.setStroke(strokes[i]);
g2.setColor(colors[i]);
g2.draw(at.createTransformedShape(lines[i]));
g2.draw(at.createTransformedShape(path));
}
g2.setStroke(bs1);
g2.setColor(Color.black);
for (int i = 0; i < pts.length; i++) {
ellipse.setFrame(pts[i].getX(), pts[i].getY(), 9, 9);
g2.draw(ellipse);
}
}
/**
* step forward on the display: move arm forward
*/
public void step(int w, int h) {
for (int i = 0; i < lines.length; i++)
{
rAmt[i] += speed[i];
System.out.println(rAmt[i]);
if (rAmt[i] == 360) {
rAmt[i] = 0;
}
}
}
public void updateSecond(int w, int h) {
rAmt[0] += speed[0];
if (rAmt[0] == 360) {
rAmt[0] = 0;
}
}
public void updateMinute(int w, int h) {
rAmt[1] += speed[1];
if (rAmt[1] == 360) {
rAmt[1] = 0;
}
}
public void updateHour(int w, int h) {
rAmt[2] += speed[2];
if (rAmt[2] == 360) {
rAmt[2] = 0;
}
}
}
and here is the parent class that extends JPanel:
/**
* Digital Clock view base classes
*/
abstract class DigitalClockView extends JPanel
{
protected int second = 0;
protected int minute = 0;
protected int hour = 0;
public void draw()
{
this.repaint();
}
public void updateTime(int second, int minute, int hour)
{
this.second = second;
this.minute = minute;
this.hour = hour;
}
public abstract void paint(Graphics g);
}
my menu buttons are showing on the right side when I use the menu bar. It's almost as if it is just cloning the view on the left side and showing it on the right side again. Why is this?
super.repaint()
Don't do that, as calling repaint from within a paint method has potential for danger. Call the super.paint(g) method inside of a paint(Graphics g) override.
Or even better yet, don't override paint but instead override your JPanel's paintComponent(Graphics g) method and call super.paintComponent(g). For graphics you must call the same super method as the overridden method, and you're not doing that.
Also and again, you should avoid use of null layout as this makes for very inflexible GUI's that while they might look good on one platform look terrible on most other platforms or screen resolutions and that are very difficult to update and maintain.
Related
I wrote a program to render a Julia Set. The single threaded code is pretty straightforward and is essentially like so:
private Image drawFractal() {
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < WIDTH; x++) {
for (int y = 0; y < HEIGHT; y++) {
double X = map(x,0,WIDTH,-2.0,2.0);
double Y = map(y,0,HEIGHT,-1.0,1.0);
int color = getPixelColor(X,Y);
img.setRGB(x,y,color);
}
}
return img;
}
private int getPixelColor(double x, double y) {
float hue;
float saturation = 1f;
float brightness;
ComplexNumber z = new ComplexNumber(x, y);
int i;
for (i = 0; i < maxiter; i++) {
z.square();
z.add(c);
if (z.mod() > blowup) {
break;
}
}
brightness = (i < maxiter) ? 1f : 0;
hue = (i%maxiter)/(float)maxiter;
int rgb = Color.getHSBColor(hue,saturation,brightness).getRGB();
return rgb;
}
As you can see it is highly inefficient. Thus I went for Parallelizing this code using the fork/join framework in Java and this is what I came up with:
private Image drawFractal() {
BufferedImage img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
ForkCalculate fork = new ForkCalculate(img, 0, WIDTH, HEIGHT);
ForkJoinPool forkPool = new ForkJoinPool();
forkPool.invoke(fork);
return img;
}
//ForkCalculate.java
public class ForkCalculate extends RecursiveAction {
BufferedImage img;
int minWidth;
int maxWidth;
int height;
int threshold;
int numPixels;
ForkCalculate(BufferedImage b, int minW, int maxW, int h) {
img = b;
minWidth = minW;
maxWidth = maxW;
height = h;
threshold = 100000; //TODO : Experiment with this value.
numPixels = (maxWidth - minWidth) * height;
}
void computeDirectly() {
for (int x = minWidth; x < maxWidth; x++) {
for (int y = 0; y < height; y++) {
double X = map(x,0,Fractal.WIDTH,-2.0,2.0);
double Y = map(y,0,Fractal.HEIGHT,-1.0,1.0);
int color = getPixelColor(X,Y);
img.setRGB(x,y,color);
}
}
}
#Override
protected void compute() {
if(numPixels < threshold) {
computeDirectly();
return;
}
int split = (minWidth + maxWidth)/2;
invokeAll(new ForkCalculate(img, minWidth, split, height), new ForkCalculate(img, split, maxWidth, height));
}
private int getPixelColor(double x, double y) {
float hue;
float saturation = 1f;
float brightness;
ComplexNumber z = new ComplexNumber(x, y);
int i;
for (i = 0; i < Fractal.maxiter; i++) {
z.square();
z.add(Fractal.c);
if (z.mod() > Fractal.blowup) {
break;
}
}
brightness = (i < Fractal.maxiter) ? 1f : 0;
hue = (i%Fractal.maxiter)/(float)Fractal.maxiter;
int rgb = Color.getHSBColor(hue*5,saturation,brightness).getRGB();
return rgb;
}
private double map(double x, double in_min, double in_max, double out_min, double out_max) {
return (x-in_min)*(out_max-out_min)/(in_max-in_min) + out_min;
}
}
I tested with a range of values varying the maxiter, blowup and threshold.
I made the threshold such that the number of threads are around the same as the number of cores that I have (4).
I measured the runtimes in both cases and expected some optimization in parallelized code. However the code ran in the same time if not slower sometimes. This has me baffled. Is this happening because the problem size isn't big enough? I also tested with varying image sizes ranging from 640*400 to 1020*720.
Why is this happening? How can I run the code parallely so that it runs faster as it should?
Edit
If you want to checkout the code in its entirety head over to my Github
The master branch has the single threaded code.
The branch with the name Multicore has the Parallelized code.
Edit 2 Image of the fractal for reference.
Here is your code rewritten to use concurrency. I found that my Lenovo Yoga misreported the number of processors by double. Also Windows 10 seems to take up an enormous amount of processing, so the results on my laptop are dubious. If you have more cores or a decent OS, it should be much better.
package au.net.qlt.canvas.test;
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
public class TestConcurrency extends JPanel {
private BufferedImage screen;
final Fractal fractal;
private TestConcurrency(final Fractal f, Size size) {
fractal = f;
screen = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
setBackground(Color.BLACK);
setPreferredSize(new Dimension(size.width,size.height));
}
public void test(boolean CONCURRENT) {
int count = CONCURRENT ? Runtime.getRuntime().availableProcessors()/2 : 1;
Scheduler scheduler = new Scheduler(fractal.size);
Thread[] threads = new Thread[count];
long startTime = System.currentTimeMillis();
for (int p = 0; p < count; p++) {
threads[p] = new Thread() {
public void run() {
scheduler.schedule(fractal,screen);
}
};
threads[p].start();
try {
threads[p].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
DEBUG("test threads: %d - elasped time: %dms", count, (System.currentTimeMillis()-startTime));
}
#Override public void paint(Graphics g) {
if(g==null) return;
g.drawImage(screen, 0,0, null);
}
public static void main(String[]args) {
JFrame frame = new JFrame("FRACTAL");
Size size = new Size(1024, 768);
Fractal fractal = new Fractal(size);
TestConcurrency test = new TestConcurrency(fractal, size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(test);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
for(int i=1; i<=10; i++) {
DEBUG("--- Test %d ------------------", i);
test.test(false);
test.repaint();
test.test(true);
test.repaint();
}
}
public static void DEBUG(String str, Object ... args) { Also System.out.println(String.format(str, args)); }
}
class Fractal {
ComplexNumber C;
private int maxiter;
private int blowup;
private double real;
private double imaginary;
private static double xs = -2.0, xe = 2.0, ys = -1.0, ye = 1.0;
public Size size;
Fractal(Size sz){
size = sz;
real = -0.8;
imaginary = 0.156;
C = new ComplexNumber(real, imaginary);
maxiter = 400;
blowup = 4;
}
public int getPixelColor(Ref ref) {
float hue;
float saturation = 1f;
float brightness;
double X = map(ref.x,0,size.width,xs,xe);
double Y = map(ref.y,0,size.height,ys,ye);
ComplexNumber Z = new ComplexNumber(X, Y);
int i;
for (i = 0; i < maxiter; i++) {
Z.square();
Z.add(C);
if (Z.mod() > blowup) {
break;
}
}
brightness = (i < maxiter) ? 1f : 0;
hue = (i%maxiter)/(float)maxiter;
return Color.getHSBColor(hue*5,saturation,brightness).getRGB();
}
private double map(double n, double in_min, double in_max, double out_min, double out_max) {
return (n-in_min)*(out_max-out_min)/(in_max-in_min) + out_min;
}
}
class Size{
int width, height, length;
public Size(int w, int h) { width = w; height = h; length = h*w; }
}
class ComplexNumber {
private double real;
private double imaginary;
ComplexNumber(double a, double b) {
real = a;
imaginary = b;
}
void square() {
double new_real = Math.pow(real,2) - Math.pow(imaginary,2);
double new_imaginary = 2*real*imaginary;
this.real = new_real;
this.imaginary = new_imaginary;
}
double mod() {
return Math.sqrt(Math.pow(real,2) + Math.pow(imaginary,2));
}
void add(ComplexNumber c) {
this.real += c.real;
this.imaginary += c.imaginary;
}
}
class Scheduler {
private Size size;
private int x, y, index;
private final Object nextSync = 4;
public Scheduler(Size sz) { size = sz; }
/**
* Update the ref object with next available coords,
* return false if no more coords to be had (image is rendered)
*
* #param ref Ref object to be updated
* #return false if end of image reached
*/
public boolean next(Ref ref) {
synchronized (nextSync) {
// load passed in ref
ref.x = x;
ref.y = y;
ref.index = index;
if (++index > size.length) return false; // end of the image
// load local counters for next access
if (++x >= size.width) {
x = 0;
y++;
}
return true; // there are more pixels to be had
}
}
public void schedule(Fractal fractal, BufferedImage screen) {
for(Ref ref = new Ref(); next(ref);)
screen.setRGB(ref.x, ref.y, fractal.getPixelColor(ref));
}
}
class Ref {
public int x, y, index;
public Ref() {}
}
I've been trying to generate a grid of randomly colored boxes for a brick-breaker game. However given this code, the colors keep changing. I'd like them to be randomly set and stay that way.
for(int i = 0; i < map.length; i++) {
for(int j = 0; j < map [0].length; j++) {
if(map[i][j] > 0) { //make brick if greater than 0, else don't
int color = (int) (Math.random() * 256);
g.setColor(new Color(color, color, color));
g.fillRect(j * brickWidth + 80, i * brickHeight + 50, brickWidth, brickHeight);
g.setStroke(new BasicStroke(3));
g.setColor(Color.black);
g.drawRect(j * brickWidth + 80, i * brickHeight + 50, brickWidth, brickHeight);
}
}
}
Each time your component need to be resized or moved, repaint() method is being called to update it's state. So if you are generating colors in paintComponent like in following example:
public class MyComponent extends JComponent {
#Override
protected void paintComponent(Graphics g) {
// generate colors and draw grid
}
}
then colors will change on resize event or other event that lead to repaint call, since repaint invokes paintComponent method. If you want to display the same colors, just move generate-colors code out of this method:
public class MyComponent extends JComponent {
private final Color[][] gridColors = randomGridColors(5, 5);
private Color[][] randomGridColors(int rows, int columns) {
Color[][] gridColors = new Color[rows][columns];
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++) {
gridColors [i][j] = randomColor();
}
}
}
private Color randomColor() {
int rgbValue = (int) (Math.random() * 256);
return new Color(rgbValue, rgbValue, rgbValue);
}
#Override
protected void paintComponent(Graphics g) {
// draw grid
}
}
You currently create the random color every time you draw your bricks, which can be many times per second.
Create some brick class and generate the color for each brick only once.
Something like this:
public class TryThis {
private static final Logger LOG = Logger.getLogger(TryThis.class.getName());
public static void main(String[] args) {
SwingComponent panel = new SwingComponent();
JFrame frame = new JFrame("try me");
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(panel);
frame.setVisible(true);
}
static class SwingComponent extends JPanel {
int W = 10;
int H = 10;
int recwidth = 20;
int recheight = 10;
Brick[][] bricks = new Brick[H][W];
public SwingComponent() {
super();
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
bricks[y][x] = new Brick(createRandomColor(), new Rectangle(x * recwidth, y * recheight, recwidth,
recheight));
}
}
}
#Override
public void paint(Graphics g) {
for (int y = 0; y < H; y++) {
for (int x = 0; x < W; x++) {
bricks[y][x].draw(g);
}
}
}
Color createRandomColor() {
return new Color((int) (Math.random() * 256), (int) (Math.random() * 256), (int) (Math.random() * 256));
}
}
static class Brick {
Color col;
Rectangle rec;
public Brick(Color col, Rectangle rec) {
this.col = col;
this.rec = rec;
}
public void draw(Graphics g) {
g.setColor(col);
g.fillRect(rec.x, rec.y, rec.width, rec.height);
}
}
}
I have a problem with a hexagonal grid. I found this code you can see below on Internet, so it's not mine. There are two public classes: hexgame which generates the grid and hexmech which draws and fills every single hexagon. What I'd like to do is basically insert an image into a specific hexagon, but I don't know how to code this and in which part of the classes I should put it. Am I thinking the wrong way?
Thank you very much for your help!
Hexgame
package hex;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class hexgame
{
private hexgame() {
initGame();
createAndShowGUI();
}
final static Color COLOURBACK = Color.WHITE;
final static Color COLOURCELL = Color.WHITE;
final static Color COLOURGRID = Color.BLACK;
final static Color COLOURONE = new Color(255,255,255,200);
final static Color COLOURONETXT = Color.BLUE;
final static Color COLOURTWO = new Color(0,0,0,200);
final static Color COLOURTWOTXT = new Color(255,100,255);
final static Color COLOURSAFE = Color.WHITE;
final static Color COLOURDANGEROUS = Color.LIGHT_GRAY;
final static int EMPTY = 0;
final static int UNKNOWN = -1;
final static int SAFE = 1;
final static int DANGEROUS = 2;
final static int CLICKED = 3;
final static int COLUMN_SIZE = 23;
final static int ROW_SIZE = 14;
final static int HEXSIZE = 45;
final static int BORDERS = 15;
int[][] board = new int[COLUMN_SIZE][ROW_SIZE];
void initGame(){
hexmech.setXYasVertex(false);
hexmech.setHeight(HEXSIZE);
hexmech.setBorders(BORDERS);
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
board[i][j]=EMPTY;
}
}
board[5][5] = SAFE;
board[5][6] = SAFE;
board[5][7] = SAFE;
board[6][5] = SAFE;
board [6][6] = SAFE;
board[4][4] = UNKNOWN;
}
private void createAndShowGUI()
{
DrawingPanel panel = new DrawingPanel();
JFrame frame = new JFrame("Hex Testing 4");
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
Container content = frame.getContentPane();
content.add(panel);
frame.setSize(825, 630);
frame.setResizable(true);
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
class DrawingPanel extends JPanel
{
public DrawingPanel()
{
setBackground(COLOURBACK);
MyMouseListener ml = new MyMouseListener();
addMouseListener(ml);
}
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setFont(new Font("TimesRoman", Font.PLAIN, 15));
super.paintComponent(g2);
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
if (board[i][j] != UNKNOWN)
hexmech.drawHex(i,j,g2);
}
}
for (int i=0;i<COLUMN_SIZE;i++) {
for (int j=0;j<ROW_SIZE;j++) {
if (board[i][j] != UNKNOWN)
hexmech.fillHex(i,j,board[i][j],g2);
}
}
}
class MyMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent e) {
int x = e.getX();
int y = e.getY();
Point p = new Point( hexmech.pxtoHex(e.getX(),e.getY()) );
if (p.x < 0 || p.y < 0 || p.x >= COLUMN_SIZE || p.y >= ROW_SIZE) return;
board[p.x][p.y] = CLICKED;
repaint();
}
}
}
}
Hexmech
package hex;
import java.awt.*;
import javax.swing.*;
public class hexmech
{
#define HEXEAST 0
#define HEXSOUTHEAST 1
#define HEXSOUTHWEST 2
#define HEXWEST 3
#define HEXNORTHWEST 4
#define HEXNORTHEAST 5
public final static boolean orFLAT= true;
public final static boolean orPOINT= false;
public static boolean ORIENT= orFLAT;
public static boolean XYVertex=true;
private static int BORDERS=50
private static int s=0; // length of one side
private static int t=0; // short side of 30o triangle outside of each hex
private static int r=0; // radius of inscribed circle (centre to middle of each side). r= h/2
private static int h=0; // height. Distance between centres of two adjacent hexes. Distance between two opposite sides in a hex.
public static void setXYasVertex(boolean b) {
XYVertex=b;
}
public static void setBorders(int b){
BORDERS=b;
}
public static void setSide(int side) {
s=side;
t = (int) (s / 2); //t = s sin(30) = (int) CalculateH(s);
r = (int) (s * 0.8660254037844);
h=2*r;
}
public static void setHeight(int height) {
h = height;
r = h/2; // r = radius of inscribed circle
s = (int) (h / 1.73205); // s = (h/2)/cos(30)= (h/2) / (sqrt(3)/2) = h / sqrt(3)
t = (int) (r / 1.73205); // t = (h/2) tan30 = (h/2) 1/sqrt(3) = h / (2 sqrt(3)) = r / sqrt(3)
}
public static Polygon hex (int x0, int y0) {
int y = y0 + BORDERS;
int x = x0 + BORDERS;
if (s == 0 || h == 0) {
System.out.println("ERROR: size of hex has not been set");
return new Polygon();
}
int[] cx,cy;
if (XYVertex)
cx = new int[] {x,x+s,x+s+t,x+s,x,x-t}; //this is for the top left vertex being at x,y. Which means that some of the hex is cutoff.
else
cx = new int[] {x+t,x+s+t,x+s+t+t,x+s+t,x+t,x}; //this is for the whole hexagon to be below and to the right of this point
cy = new int[] {y,y,y+r,y+r+r,y+r+r,y+r};
return new Polygon(cx,cy,6);
}
public static void drawHex(int i, int j, Graphics2D g2) {
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
Polygon poly = hex(x,y);
g2.setColor(hexgame.COLOURCELL);
//g2.fillPolygon(hexmech.hex(x,y));
g2.fillPolygon(poly);
g2.setColor(hexgame.COLOURGRID);
g2.drawString(String.format("%c;%d", 'A'+i, j+1), x+20, y+40);
g2.drawPolygon(poly);
}
public static void fillHex(int i, int j, int n, Graphics2D g2) {
char c='o';
int x = i * (s+t);
int y = j * h + (i%2) * h/2;
/*if (n < 0) {
g2.setColor(hexgame.COLOURONE);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURONETXT);
c = (char)(-n);
g2.drawString(""+c, x+r+BORDERS, y+r+BORDERS+4); //FIXME: handle XYVertex
//g2.drawString(x+","+y, x+r+BORDERS, y+r+BORDERS+4);
}
if (n > 0) {
g2.setColor(hexgame.COLOURTWO);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURTWOTXT);
c = (char)n;
if (n==3) {
g2.setColor(hexgame.COLOURTWO);
g2.fillPolygon(hex(x,y));
g2.setColor(hexgame.COLOURTWOTXT);
}
}
public static Point pxtoHex(int mx, int my) {
Point p = new Point(-1,-1);
//correction for BORDERS and XYVertex
mx -= BORDERS;
my -= BORDERS;
if (XYVertex) mx += t;
int x = (int) (mx / (s+t));
int y = (int) ((my - (x%2)*r)/h);
int dx = mx - x*(s+t);
int dy = my - y*h;
if (my - (x%2)*r < 0) return p; // prevent clicking in the open halfhexes at the top of the screen
//System.out.println("dx=" + dx + " dy=" + dy + " > " + dx*r/t + " <");
//even columns
if (x%2==0) {
if (dy > r) { //bottom half of hexes
if (dx * r /t < dy - r) {
x--;
}
}
if (dy < r) { //top half of hexes
if ((t - dx)*r/t > dy ) {
x--;
y--;
}
}
} else { // odd columns
if (dy > h) { //bottom half of hexes
if (dx * r/t < dy - h) {
x--;
y++;
}
}
if (dy < h) { //top half of hexes
//System.out.println("" + (t- dx)*r/t + " " + (dy - r));
if ((t - dx)*r/t > dy - r) {
x--;
}
}
}
p.x=x;
p.y=y;
return p;
}
In your implementation of paintComponent(), invoke setClip() with a suitable Shape, such as Polygon. You can size and translate the Polygon to match the destination hexagon using the createTransformedShape() method of AffineTransform. Use the coordinates of the polygon's boundary as the basis for the coordinates used in your call to drawImage(). A related example using Ellipse2D is shown here.
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);
Here's my original question on SO kindly answered. The height is now set at what I think is the correct size. But I can't see the bottom 2/3s of the panel.
I have read, and asked, and mused, and experimented, but I still cannot find an answer. I don't need code, just a little help.
My JFrame class;
public Frame(String title) throws FileNotFoundException {
super(String.format("Title", title));
this.panel = new Panel();
this.panel.drawLinesAndTab();
this.panel.setSize(this.panel.getPreferredSize());
this.panel.validate();
this.scroller = new JScrollPane(this.panel);
//this.scroller.setPreferredSize(new Dimension(this.panel.getPreferredSize()));
this.scroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
this.scroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
//this.scroller.setSize(new Dimension(this.panel.getPreferredSize()));
this.scroller.getVerticalScrollBar().setUnitIncrement(20);
this.getContentPane().add(this.scroller);
//this.pack();
}
and this is the JPanel class. I know it's huge, and I do have plans to re-write this code, but I'm under time limitations and have to try and get it at least seeing all of the output.
public Panel() throws FileNotFoundException {
this.tab = new ReadTabFile("tabSource.txt");
this.image = new BufferedImage(this.width, this.height, BufferedImage.TYPE_INT_RGB);
Graphics g = this.image.createGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, this.image.getWidth(), this.image.getHeight());
setBorder(BorderFactory.createLineBorder(Color.black));
this.setFocusable(true);
}
public void drawLinesAndTab() {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
this.list = tab.readTabCode();
this.a = 20;
this.b = 100;
this.c = 60;
this.x = 40;
this.y = 100;
this.beginBarlineX = 20;
this.beginBarlineY = 100;
this.endBarY = 980;
this.endBarY = 100;
this.title = tab.getTitle();
g.drawString(this.title, 40, 20);
for (int i = 0; i < this.list.size(); i++) {
Bar theBar = (Bar) this.list.get(i);
drawBarline(a, b, a, b + 125);
ArrayList<String> stuff = theBar.getLinesInBar();
for (int j = 0; j < stuff.size(); j++) {
String line = stuff.get(j);
theFlag = line.substring(0, 1);
theNotes = line.substring(1, line.length());
if (newLine = true) {
}
try {
System.out.println(theNotes);
if (c <= (width - 40)) {
newLine = false;
String zero = theFlag;
drawFlag(zero, x + 5, y - 20);
String one = theNotes.substring(0, 1);
g.drawLine(a, b, c, b);
drawLetter(one, x, y);
String two = theNotes.substring(1, 2);
drawLetter(two, x, y += 25);
g.drawLine(a, b += 25, c, b);
String three = theNotes.substring(2, 3);
drawLetter(three, x, y += 25);
g.drawLine(a, b += 25, c, b);
String four = theNotes.substring(3, 4);
drawLetter(four, x, y += 25);
g.drawLine(a, b += 25, c, b);
String five = theNotes.substring(4, 5);
drawLetter(five, x, y += 25);
g.drawLine(a, b += 25, c, b);
String six = theNotes.substring(5, 6);
drawLetter(six, x, y += 25);
g.drawLine(a, b += 25, c, b);
this.repaint();
b -= 125;
y -= 125;
x += 40;
a += 40;
c += 40;
} else {
if (height < (b - 100)) {
height += 205;
}
newLine = true;
a = 20;
x = 20;
b += 225;
c = 60;
y += 225;
beginBarlineX = 20;
beginBarlineY += 100;
endBarX += 100;
endBarY = 100;
this.repaint();
}
} catch (Exception ex) {
System.err.println(ex + " within if drawtab/line for loop");
}
}
}
}
public void drawBarline(int xTop, int yTop, int xBot, int yBot) {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.drawLine(xTop, yTop, xBot, yBot);
}
public Point makeBarline(int xTop, int yTop, int xBot, int yBot) {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.drawLine(xTop, yTop, xBot, yBot);
return (new Point());
}
public Point drawLetter(String letter, int x, int y) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.setFont(letterFont(letter).deriveFont(20.0f));
g.drawString(letter, x, y);
return (new Point());
}
public Point drawFlag(String letter, int x, int y) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
g.setFont(flagFont(letter).deriveFont(30.0f));
g.drawString(letter, x, y);
return (new Point());
}
public Font letterFont(String fontString) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
if (!Character.isDigit(fontString.charAt(0))) {
this.letterFont = Font.createFont(Font.TRUETYPE_FONT, new File("LeRoy.ttf"));
g.getFontMetrics(this.letterFont);
g.setFont(this.letterFont);
return this.letterFont;
} else {
return null;
}
}
public Font flagFont(String fontString) throws FontFormatException, IOException {
Graphics g = this.image.getGraphics();
g.setColor(Color.black);
if (!Character.isDigit(fontString.charAt(0))) {
this.flagFont = Font.createFont(Font.TRUETYPE_FONT, new File("LeroyLuteNotes1.ttf"));
g.getFontMetrics(this.flagFont);
g.setFont(this.flagFont);
return this.flagFont;
} else {
return null;
}
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(this.width, this.height));
}
public BufferedImage getImage() {
return this.image;
}
#Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics g = graphics.create();
g.drawImage(this.image, 0, 0, null);
}
}
I'm not sure what you are attempting to do with that code. It looks like you might be trying to do some custom painting on top of an image. If so then I have the following suggestions:
public Dimension getPreferredSize() {
return (new Dimension(this.width, this.height));
}
This makes no sense, you are saying that the preferred size is equal to the actual size of the component. The preferred size should be the size of the image.
Your custom painting code is completely wrong. All custom painting code should be done from the paintComponent() method. So first you would paint the image as the background of your component. Then you would invoke the other painting methods to paint stuff on top of the image. You would pass the Graphics object from the paintComponent() method to all of these other painting methods, instead of using image.getGraphics().
Start with a simple custom painting example from the Swing tutorial on Custom Painting. Once you learn the basics you customize your code one step at a time. That is first paint the background. Make sure the size is correct and scrolling works. Then move to the next step and add another method to paint something else on top of the image.
I never understand why people write hundreds of lines of code without doing basic testing along the way to make sure the code is working.