Related
There are balls in my app that just fly through display. They draws as I want. But now I want to draw the trail behind them.
All I could make is just drawing by canvas.drawPath something like following picture:
But it is not what I want. It should have pointed tail and gradient color like this:
I have no idea how to make it. Tried BitmapShader - couldn't make something right. Help, please.
Code:
First of all, there is Point class for position on display:
class Point {
float x, y;
...
}
And trail is stored as queue of Point:
private ConcurrentLinkedQueue<Point> trail;
It doesn't matter how it fills, just know it has size limit:
trail.add(position);
if(trail.size() > TRAIL_MAX_COUNT) {
trail.remove();
}
And drawing happened in DrawTrail method:
private void DrawTrail(Canvas canvas) {
trailPath.reset();
boolean isFirst = true;
for(Point p : trail) {
if(isFirst) {
trailPath.moveTo(p.x, p.y);
isFirst = false;
} else {
trailPath.lineTo(p.x, p.y);
}
}
canvas.drawPath(trailPath, trailPaint);
}
By the way, trailPaint is just really fat paint :)
trailPaint = new Paint();
trailPaint.setStyle(Paint.Style.STROKE);
trailPaint.setColor(color);
trailPaint.setStrokeWidth(radius * 2);
trailPaint.setAlpha(150);
I see you want to see a gradient on the ball path, you could use something like this
int x1 = 0, y1 = 0, x2 = 0, y2 = 40;
Shader shader = new LinearGradient(0, 0, 0, 40, Color.WHITE, Color.BLACK, TileMode.CLAMP);
trailPaint = new Paint();
trailPaint.setShader(shader);
This is what you should change your trailPaint to and see if it works.
provided from here.
I found solution. But still think it is not the best one.
First of all there are my class fields used for that task.
static final int TRAIL_MAX_COUNT = 50; //maximum trail array size
static final int TRAIL_DRAW_POINT = 30; //number of points to split the trail for draw
private ConcurrentLinkedQueue<Point> trail;
private Paint[] trailPaints;
private float[][] trailPoss, trailTans;
private Path trailPath;
Additionally to trailPath object I used PathMeasure object to split path to multiple equal parts.
After filling trail array object added call of trail calculating function.
lastTrailAdd = now;
trail.add(pos.Copy());
if (trail.size() > TRAIL_MAX_COUNT) {
trail.remove();
}
FillTrail();
Then my FillTrail function.
private void FillTrail() {
trailPath.reset();
boolean isFirst = true;
for(Point p : trail) {
if(isFirst) {
trailPath.moveTo(p.x, p.y);
trailPoss[0][0] = p.x;
trailPoss[0][1] = p.y;
isFirst = false;
} else {
trailPath.lineTo(p.x, p.y);
}
}
PathMeasure path = new PathMeasure(trailPath, false);
float step = path.getLength() / TRAIL_DRAW_POINT;
for(int i=0; i<TRAIL_DRAW_POINT; i++) {
path.getPosTan(step * i, trailPoss[i], trailTans[i]);
}
}
It separated from drawing thread. Next code is drawing function.
private void DrawTrail(Canvas canvas) {
if(trail.size() > 1) {
float prevWidthHalfX = 0f, prevWidthHalfY = 0f, prevX = 0f, prevY = 0f;
Path trailStepRect = new Path();
boolean isFirst = true;
for (int i = 0; i < TRAIL_DRAW_POINT; i++) {
float currWidthHalf = (float) (radius) * i / TRAIL_DRAW_POINT / 2f,
currWidthHalfX = currWidthHalf * trailTans[i][1],
currWidthHalfY = currWidthHalf * trailTans[i][0],
currX = trailPoss[i][0], currY = trailPoss[i][1];
if (!isFirst) {
trailStepRect.reset();
trailStepRect.moveTo(prevX - prevWidthHalfX, prevY + prevWidthHalfY);
trailStepRect.lineTo(prevX + prevWidthHalfX, prevY - prevWidthHalfY);
trailStepRect.lineTo(currX + currWidthHalfX, currY - currWidthHalfY);
trailStepRect.lineTo(currX - currWidthHalfX, currY + currWidthHalfY);
canvas.drawPath(trailStepRect, trailPaints[i]);
} else {
isFirst = false;
}
prevX = currX;
prevY = currY;
prevWidthHalfX = currWidthHalfX;
prevWidthHalfY = currWidthHalfY;
}
}
}
Main point of this is drawing trail by parts with different paints. Closer to ball - wider the trail. I think I will optimise it, but it is allready work.
If you want to watch how it looks just install my app from google play.
I've got a little problem with aligning a group of Strings to the bottom of a box in Java.
NOTE: the Box class I'm using is not a default > javax.Swing box! It's a simple costum box with a x, y position, and with a width and height!
What do I currently have?
A Message class which can be individually aligned by my Allign class.
A MessageList object containing an ArrayList of Message objects which can be aligned together with the Allign class.
A Box object that contains the position and dimension of the box. The Allign class uses this box to align the objects in.
The Allign class that can align different types of Objects, and use a Box object to align in.
How does my code should work:
(code snippets further down the page)
The Message object can use different Font settings. The Allign class can align these Messages objects. The Message class contains a method called obtainFontDimension(), which gets the bounds of the object's String in the preferred Font settings. When we want to apply an alignment to a Message object, I create a Box object which contains the x,y position and the width and height. By calling Allign.applyAllignment(params) the calculations will be applied to align the Message object in the Box, and the Message's dx, dy (drawing x, y positions) will be set.
Till here it works fine.
Now I create a MessageList object and add some (Message objects to it. When applying an alignment to this, it will run through the Message objects it contains, and will call the obtainFontDimensions() on them. The height of these Strings will be summed, and results into a total height (int listHeight) of the Strings together. To get the drawing position of each Message object, we take the y-position of the Box where we want to align in. First we remove the listHeight of the Box's y position:
Now we got the offset of the first String. When the bottom alignment is being applied, it just adds the height of the Box to the offset. Finally, the offset is set for the next Message object by adding the current Message object height to te offset. Time for the next iteration, till the ArrayList has been fully calculated. This should result in the following:
What is going wrong?
When applying an alignment to a MessageList object, some Strings touch eachother perfectly (see circle B on image), and some keep some pixels more distance then others (see circle A1, A2 on image). And next to that, there remains an unexpected padding on the bottom (see circle C on image).
What have I attempted so far?
First I've been checking the height of the Strings, which all seem to be correct. So the `obtainFontDimensions()` method seems to be working fine.
Drawing the concept on paper, and attempt to recalculate the procedure, which should get me the correct position of the Strings. For example:
- `Box`: x=80, y=80, width=100, height=100
- `MessageList` with 3 `Messages`, which have a height of 0=10, 1=10, 2=20. Total height of these Strings is 40 pixels.
- Before actually alligning, the position of the first String becomes box.y-listHeight, which is 40.
- When actually alligning to the bottom, the the offset becomes 40+100=140.
- The offset for the second String will be calculated: 140+20(current message's height)=160.
- This repeats for the third string, which is is 160+10 = 170.
- That means, that the bottom line of the final string is on 170+10 = 180, which equals the bottom of the `Box`'s bottom.
Your suggestion...?
Code snippets
(only important parts)
public class Window()
{
public void draw(Graphics2D g2d)
{
Box contentBox = new Box(100, 100, 300, 300);
Message loadTitle = new Message("This is a testing TITLE", Colors.ORANGE, Fonts.LOADING_TITLE, false);
Message loadDescription = new Message(loadString, Colors.RED, Fonts.LOADING_DESCRIPTION, false);
Message loadTip = new Message("This is a random TIP!", Colors.RED, Fonts.LOADING_DESCRIPTION, false);
Message loadRelease = new Message("Planned game release 2939!", Colors.RED, Fonts.LOADING_DESCRIPTION, false);
Message loadSingle = new Message("THIS IS A SINGLE MESSAGE! 2o15", Colors.RED, Fonts.LOADING_DESCRIPTION, false);
MessageList list = new MessageList();
list.add(loadTitle);
list.add(loadDescription);
list.add(loadTip);
list.add(loadRelease);
list.add(loadSingle);
Allign.applyAllignment(g2d, Allignment.BOTTOM_RIGHT, list, loadBox);
loadBox.testDraw(g2d);
loadTitle.draw(g2d);
loadDescription.draw(g2d);
loadTip.draw(g2d);
loadRelease.draw(g2d);
loadSingle.draw(g2d);
}
}
public class Message
{
private String text;
private Color color;
private Font font;
private int dx, dy;
private int width, height;
private Rectancle2D vb; // temp
public Message(String text, int x, int y, Color color, Font font)
{
// set text, color, font, x, y..
}
public Rectangle2D obtainFontDimension(Graphics2D g2d)
{
if(font == null){ font = Fonts.DEFAULT; }
g2d.setFont(font);
FontRenderContext frc = g2d.getFontRenderContext();
GlyphVector gv = g2d.getFont().createGlyphVector(frc, text);
Rectangle2D vb = gv.getPixelBounds(null, 0, 0);
this.width = (int)vb.getWidth();
this.height = (int)vb.getHeight();
this.gv = gv; // TEMP for bound drawing
return vb;
}
public void draw(Graphics2D g2d)
{
g2d.setFont(font);
g2d.setColor(color);
g2d.drawString(text, dx, dy);
// TEMP draw bounds
g2d.setColor(new Color(0, 0, 0, 100));
g2d.draw(gv.getPixelBounds(null, dx, dy));
}
}
public class Allign
{
public static enum Allignment
{
BOTTOM_RIGHT
//, etc
}
public static void applyAllignment(Graphics2D g2d, Allignment allignment, Object object, Box box)
{
Point position = null;
Point dimension = null;
if(obj instanceof Message){ // Single Message object }
else if(obj instanceof Message)
{
MessageList messageList = (MessageList) obj;
int listHeight = 0;
for(Message message : messageList.getList())
{
listHeight += message.obtainFontDimension(g2d).getHeight();
}
position = new Point(box.x, box.y-listHeight); // offset Y
for(Message message : messageList.getList())
{
message.setDrawPosition(allign(allignment, position, new Dimension(message.getWidth(), 0), box, true));
position.y += message.getHeight();
}
}
}
private static Point allign(Allignment allignment, Point position, Dimension dimension, Box box, boolean verticalAllign)
{
switch(allignment)
{
case BOTTOM_RIGHT:
position = allignRight(position, dimension, box);
if(!verticalAllign) break;
position = allignBottom(position, dimension, box);
break;
// Rest
}
}
private static Point allignBottom(Point position, Dimension dimension, Box box)
{
return new Point(position.x, position.y+box.height-dimension.height);
}
}
Can I suggest a utility class like this?
public class TextPrinter {
public enum VerticalAlign {
TOP,
MIDDLE,
BOTTOM
}
public enum HorizontalAlign {
LEFT,
CENTER,
RIGHT
}
private Font font;
private Color color;
private int width;
private int height;
private VerticalAlign vAlign = VerticalAlign.TOP;
private HorizontalAlign hAlign = HorizontalAlign.LEFT;
public Font getFont() {
return font;
}
public void setFont(Font font) {
this.font = font;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public VerticalAlign getVerticalAlign() {
return vAlign;
}
public void setVerticalAlign(VerticalAlign vAlign) {
this.vAlign = vAlign;
}
public HorizontalAlign getHorizontalAlign() {
return hAlign;
}
public void setHorizontalAlign(HorizontalAlign hAlign) {
this.hAlign = hAlign;
}
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
private int getOffSetX(int widthText){
int result = 0;
if (hAlign == HorizontalAlign.CENTER){
result = (width - widthText)/2;
} else if (hAlign == HorizontalAlign.RIGHT){
result = width - widthText;
}
return result;
}
private int getOffSetY(int ascent, int descent){
int result = ascent;
if (vAlign == VerticalAlign.MIDDLE){
result = (height + ascent - descent)/2;
} else if (vAlign == VerticalAlign.BOTTOM){
result = height - descent;
}
return result;
}
public void print(Graphics g, String text, int x, int y) {
g.setColor(color);
g.setFont(font);
FontMetrics fm = g.getFontMetrics(font);
int widthText = fm.stringWidth(text);
g.drawString(text, x + getOffSetX(widthText), y + getOffSetY(fm.getAscent(), fm.getDescent()));
}
}
I use it in my poker game:
https://github.com/dperezcabrera/jpoker
Thanks David Perez and VGR for the inputs!
I have switched back to the Font metrics, grabbed the height of the string's bounds. However, this only uses the height of the baseline to the highest ascending point. For a proper bottom spacing (like on top) I added the descent integer aswell.
public int obtainFontDimension(Graphics2D g2d)
{
if(font == null){ font = Fonts.DEFAULT; }
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics(font);
Rectangle2D sb = fm.getStringBounds(text, g2d);
this.width = (int)sb.getWidth();
this.height = (int)sb.getHeight();
this.descent = (int)fm.getDescent(); // added
tempShape = new Rectangle(width, height+descent); // Temp for drawing bounds
return height;
}
Before the allignment begins, I first calculate the total height of all of the Strings in the MessageList. The total height is the height of the String including the descending height.
Then I need to get the offset height for each vertical allignment possibility, which I've added. (TOP, MIDDLE, BOTTOM)
After that, we allign every Message under eachother by adding the height and descent to the offset on every itteration, with vertical allignment disabled this time, or it will reallign every Message horizontally as a single Message object instead as a group of Messages, but it does allow it to allign vertically.
if (obj instanceof MessageList)
{
MessageList messageList = (MessageList) obj;
int listHeight = 0;
for(Message message : messageList.getList())
{
message.obtainFontDimension(g2d);
listHeight += message.getHeight()+message.getDescent();
}
position = new Point(box.x, box.y);
Dimension listDimension = new Dimension(0, listHeight);
if( allignment == Allignment.MIDDLE || allignment == Allignment.MIDDLE_LEFT
|| allignment == Allignment.ABSOLUTE_MIDDLE || allignment == Allignment.MIDDLE_RIGHT)
{
position = allign(Allignment.MIDDLE, position, listDimension, box, true);
}
else if(allignment == Allignment.BOTTOM || allignment == Allignment.BOTTOM_LEFT
|| allignment == Allignment.BOTTOM_CENTER || allignment == Allignment.BOTTOM_RIGHT)
{
position = allign(Allignment.BOTTOM, position, listDimension, box, true);
}
else
{
position = allign(Allignment.TOP, position, listDimension, box, true);
}
for(Message message : messageList.getList())
{
position.y += message.getHeight(); // prepare the offset
message.setDrawPosition(allign(allignment, position, new Dimension(message.getWidth(), 0), box, true));
position.y += message.getDescent(); // add descending offset for next itteration
}
}
To draw the Messages with the new bounds:
public void draw(Graphics2D g2d)
{
g2d.setFont(font);
g2d.setColor(color);
g2d.drawString(text, dx, dy);
// Drawing bounds for testing
g2d.setColor(new Color(0, 0, 0, 100));
shape.setLocation(dx, dy-height);
g2d.draw(tempShape);
}
Final result:
Thanks again!
Im having difficulty drawing a Sub Image of a Buffered Image everytime the Mouse Pointer Location equals that of the each border of the JPanel. The problem is that the BufferedImage that is equals the SubImage wont display
Here is the JPanel the initialization might not be correct Im still learning the components of Java and 2D graphics.
public class Map extends JPanel implements MouseListener, MouseMotionListener {
private final int SCR_W = 800;
private final int SCR_H = 600;
private int x;
private int y;
private int dx;
private int dy;
String dir = "C:\\imgs\\war\\";
private BufferedImage map_buffer;
public BufferedImage scr_buffer;
public void initScreen(int x, int y, int stage){
if(stage == 0){
try{ map_buffer = ImageIO.read(new File(dir + "map" + stage + ".jpg" ));
}catch(Exception error) { System.out.println("Error: cannot read tileset image.");
}
}
scr_buffer = map_buffer.getSubimage(x, y, SCR_W, SCR_H);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
if(scr_buffer == null)
initScreen(x, y, 0);
g.drawImage(scr_buffer, 0, 0, this);
}
boolean isLeftBorder = false;
boolean isRightBorder = false;
boolean isTopBorder = false;
boolean isBottomBorder = false;
public Map(){
addMouseListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
/**
* Check location of mouse pointer if(specified_edge)move(scr_buffer)
*
*/
System.out.println("MouseMove: " + e.getPoint().getX() + " , " + e.getPoint().getY());
if(e.getPoint().getX() == SCR_W)isRightBorder = true;
if(e.getPoint().getY() == SCR_H)isBottomBorder = true;
if(e.getPoint().getX() == 0 && e.getPoint().getY() == SCR_H)isLeftBorder = true;
if(e.getPoint().getY() == 0 && e.getPoint().getX() == SCR_W)isTopBorder = true;
if(e.getPoint().getX() != 0 && e.getPoint().getX() != SCR_W
&& e.getPoint().getY() != 0 && e.getPoint().getY() != SCR_H){
isLeftBorder = false;
isRightBorder = false;
isTopBorder = false;
isBottomBorder = false;
}
if(isRightBorder){ x += 2; repaint(); }
if(isBottomBorder){ y -= 2; repaint(); }
if(isLeftBorder){ x -= 2; repaint();}
if(isTopBorder){ y += 2; repaint(); }
}
});
}
}
In the main I init a JFrame to contain the Panel all im getting is a error
public static void main(String[] args) {
JFrame f = new JFrame("War");
f.setSize(800, 600);
f.setLayout(null);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Map m = new Map();
f.getContentPane().add(f);
f.setVisible(true);
}
In order to detect mouse movement you should use a MosuseMotionListener, while technically MouseAdapter implements this, you need to register it with the JPanel correctly
Instead of using addMouseListener, you'll want to use addMouseMotionListener instead
I'd also be worried about the use of SRC_W and SRC_H, as you can't guarantee the size of the panel. Instead, you should be using getWidth and getHeight, which will tell you the actual size of the component
You can improve the chances of obtaining the size you want by overriding the getPreferredSize and return the size you would like. You'd then use pack on the frame to wrap the frame about it
f.getContentPane().add(f); is adding the frame to itself, it should probably be more like f.getContentPane().add(m);
f.setLayout(null); will prevent any of the child components from been sized and positioned and is best avoid, just get rid of it.
Avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify
This scr_buffer = map_buffer.getSubimage(x, y, SCR_W, SCR_H); is also a little dangerous, as it could be asking for more of the image then is available, you should be testing to see if x + SCR_W < image width (and same goes for the height)
I don't know if this deliberate or not, but you never reset the "border" flags, so once set, they will always be true...
addMouseMotionListener(new MouseAdapter() {
public void mouseMoved(MouseEvent e) {
/**
* Check location of mouse pointer if(specified_edge)move(scr_buffer)
*
*/
isRightBorder = false;
isBottomBorder = false;
isTopBorder = false;
isLeftBorder = false;
You may also want to have a "space" around the edge, which when the mouse enters it, it will set the border flags, for example...
if (e.getPoint().getX() >= getWidth() - 4) {
isRightBorder = true;
}
if (e.getPoint().getY() >= getHeight() - 4) {
isBottomBorder = true;
}
if (e.getPoint().getX() <= 4) {
isLeftBorder = true;
}
if (e.getPoint().getY() <= 4) {
isTopBorder = true;
}
Your logic for the vertical movement is wrong, when the mouse is within the bottom border, it should add to the y position and subtract when it's within the top border...
if (isBottomBorder) {
y += 2;
}
if (isTopBorder) {
y -= 2;
}
You need to perform some range checking after you've modified the x/y positions to make sure you're not request for a portion of the image which is not available...
if (x < 0) {
x = 0;
} else if (x + getWidth() > map_buffer.getWidth()) {
x = map_buffer.getWidth() - getWidth();
}
if (y < 0) {
y = 0;
} else if (y + getHeight() > map_buffer.getHeight()) {
y = map_buffer.getHeight() - getHeight();
}
There is a logic error within the initScreen method, src_buffer is never set to null, meaning that once it has a "sub image", it never tries to obtain a new one (also, you shouldn't be loading the map_buffer in there either).
scr_buffer = null;
repaint();
Thank you for you time and understanding.
Inside mouse moved
if (e.getPoint().getX() >= getWidth() - 4) {
isRightBorder = true; // unnecessary
scr_buffer = null;
x = x + 2;
repaint();
}
i just finish making a Minesweeper game, everything functions perfectly except one thing, the speed of loading the images into he game. I noticed if i have a large number of cells in the game images loads really slow after the mouse click on the cell and it gets faster if i have a smaller number of cells. is there any other way that would make loading images much faster than the one i used? here is the method i used in order to load the images into the game :
private void draw(Graphics g) {
BufferedImage gRec =null, flag=null, mine=null, aCount0=null,
aCount1=null,aCount2 =null,aCount3 =null,aCount4 =null,aCount5 =null,
aCount6 =null,aCount7 =null,aCount8 = null;
try {
gRec = ImageIO.read(new File("/Users/msa_666/Desktop/blank.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
mine = ImageIO.read(new File("/Users/msa_666/Desktop/bombdeath.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
aCount0 = ImageIO.read(new File("/Users/msa_666/Desktop/open0.gif"));
aCount1 = ImageIO.read(new File("/Users/msa_666/Desktop/open1.gif"));
aCount2 = ImageIO.read(new File("/Users/msa_666/Desktop/open2.gif"));
aCount3 = ImageIO.read(new File("/Users/msa_666/Desktop/open3.gif"));
aCount4 = ImageIO.read(new File("/Users/msa_666/Desktop/open4.gif"));
aCount5 = ImageIO.read(new File("/Users/msa_666/Desktop/open5.gif"));
aCount6 = ImageIO.read(new File("/Users/msa_666/Desktop/open6.gif"));
aCount7 = ImageIO.read(new File("/Users/msa_666/Desktop/open7.gif"));
aCount8 = ImageIO.read(new File("/Users/msa_666/Desktop/open8.gif"));
}
catch (IOException e) {
e.printStackTrace();
}
if (getCovered() == true && getMarked () == false) { // gray rectangle
g.drawImage(gRec,getX(),getY(),w,h,null);
}
else if (getCovered()==true && getMarked() == true) { //flag
g.drawImage(flag,getX(),getY(),w,h,null);
}
else if (getCovered()== false && getMined()== true){ //mine
g.drawImage(mine,getX(),getY(),w,h,null);
}
else if ( getCovered() == false && getMined() == false) { // adjacency count image
switch (getAdjCount()){
case 0:
g.drawImage(aCount0,getX(),getY(),w,h,null);
break;
case 1:
g.drawImage(aCount1,getX(),getY(),w,h,null);
break;
case 2:
g.drawImage(aCount2,getX(),getY(),w,h,null);
break;
case 3:
g.drawImage(aCount3,getX(),getY(),w,h,null);
break;
case 4:
g.drawImage(aCount4,getX(),getY(),w,h,null);
break;
case 5:
g.drawImage(aCount5,getX(),getY(),w,h,null);
break;
case 6:
g.drawImage(aCount6,getX(),getY(),w,h,null);
break;
case 7:
g.drawImage(aCount7,getX(),getY(),w,h,null);
break;
case 8:
g.drawImage(aCount8,getX(),getY(),w,h,null);
break;
}
}
}
here is the mouse listener to repaint each cell after clicking on it :
public void mouseClicked(MouseEvent e) {
int sRow, sCol;
sRow= e.getX() / cellHeight;
sCol = e.getY()/ cellWidth;
System.out.println(e.getX() +"," +sCol);
System.out.println(e.getY()+","+sRow);
if (e.getButton() == MouseEvent.BUTTON1) {
if( cells[sRow][sCol].getMarked() == false)
uncoverCell(cells[sRow][sCol]);
// cells[sRow][sCol].setCovered(false);
System.out.println(cells[sRow][sCol].getMined());
repaint();
}
else if (e.getButton() == MouseEvent.BUTTON2) {
}
else if (e.getButton() == MouseEvent.BUTTON3) {
if (cells[sRow][sCol].getMarked() == false){
cells[sRow][sCol].setMarked(true);
repaint();
}
else {
cells[sRow][sCol].setMarked(false);
repaint();
}
}
if (allMinesMarked() && allNonMinesUncovered()){
System.out.println("You Win");
}
}
public void paintComponent(Graphics g) {
for ( int i=0 ; i <rowCount; i++ ) {
for (int j=0; j<columnCount; j++) {
cells[i][j].draw(g);
}
}
}
You need to tell us:
Just where is draw(...) called?
How do you obtain the Graphics object, g, that is passed into the draw method's parameter?
I'm guessing here since we don't have all of the relevant code, but it looks as if you're re-reading in your images each time you want to display one. If so, don't do this. Read the images in only once at the start of the program, and then use the Images or perhaps better, ImageIcons, obtained when you need them.
Edit
Thanks for posting more code, and this in fact confirms my suspicion: you're re-reading in the image files with every repaint of your GUI. This is highly inefficient and will slow your program down to a crawl. Again, you should read the images into your program once and then use them multiple times.
Myself I'd create ImageIcons from the images, and then display them in a JLabel. When there is need to swap images, simply call setIcon(...) on the JLabel. This way there's no need to even mess with paintComponent(...).
Edit 2
For example (compile and run this):
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SwapIcons {
private static final int CELL_SIDE_COUNT = 3;
private ImageCell[] imageCells = new ImageCell[CELL_SIDE_COUNT * CELL_SIDE_COUNT];
private JPanel mainPanel = new JPanel();
public SwapIcons(final GetImages getImages) {
mainPanel.setLayout(new GridLayout(CELL_SIDE_COUNT, CELL_SIDE_COUNT));
mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
for (int i = 0; i < imageCells.length; i++) {
imageCells[i] = new ImageCell(getImages);
mainPanel.add(imageCells[i].getImgLabel());
}
}
public JComponent getMainComponent() {
return mainPanel;
}
private static void createAndShowGui(GetImages getImages) {
SwapIcons swapIcons = new SwapIcons(getImages);
JFrame frame = new JFrame("Click on Icons to Change");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(swapIcons.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
try {
final GetImages getImages = new GetImages();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui(getImages);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ImageCell {
private JLabel imgLabel = new JLabel();
private GetImages getImages;
private int iconIndex = 0;
public ImageCell(final GetImages getImages) {
this.getImages = getImages;
imgLabel.setIcon(getImages.getIcon(0));
imgLabel.addMouseListener(new MyMouseListener());
}
public JLabel getImgLabel() {
return imgLabel;
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
iconIndex++;
iconIndex %= getImages.getIconListSize();
imgLabel.setIcon(getImages.getIcon(iconIndex));
}
}
}
// Simply gets a SpriteSheet and subdivides it into a List of ImageIcons
class GetImages {
private static final String SPRITE_PATH = "http://th02.deviantart.net/"
+ "fs70/PRE/i/2011/169/0/8/blue_player_sprite_sheet_by_resetado-d3j7zba.png";
public static final int SPRITE_ROWS = 6;
public static final int SPRITE_COLS = 6;
public static final int SPRITE_CELLS = 35;
private List<ImageIcon> iconList = new ArrayList<ImageIcon>();
public GetImages() throws IOException {
URL imgUrl = new URL(SPRITE_PATH);
BufferedImage mainImage = ImageIO.read(imgUrl);
for (int i = 0; i < SPRITE_CELLS; i++) {
int row = i / SPRITE_COLS;
int col = i % SPRITE_COLS;
int x = (int) (((double) mainImage.getWidth() * col) / SPRITE_COLS);
int y = (int) ((double) (mainImage.getHeight() * row) / SPRITE_ROWS);
int w = (int) ((double) mainImage.getWidth() / SPRITE_COLS);
int h = (int) ((double) mainImage.getHeight() / SPRITE_ROWS);
BufferedImage img = mainImage.getSubimage(x, y, w, h);
ImageIcon icon = new ImageIcon(img);
iconList.add(icon);
}
}
// get the Icon from the List at index position
public ImageIcon getIcon(int index) {
if (index < 0 || index >= iconList.size()) {
throw new ArrayIndexOutOfBoundsException(index);
}
return iconList.get(index);
}
public int getIconListSize() {
return iconList.size();
}
}
Hovercraft Full Of Eels answer is good and will work.
And is fine for a standalone app, but for an applet or web start app can further optimize by having one large image and then copying parts of it to the graphics object that is visible, think grids and use function in java.awt.Graphics object (from javadoc):
public abstract boolean drawImage(Image img,
int dx1,
int dy1,
int dx2,
int dy2,
int sx1,
int sy1,
int sx2,
int sy2,
ImageObserver observer)
Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface. Transparent pixels do not affect whatever pixels are already there.
This method returns immediately in all cases, even if the image area to be drawn has not yet been scaled, dithered, and converted for the current output device. If the current output representation is not yet complete then drawImage returns false. As more of the image becomes available, the process that loads the image notifies the specified image observer.
This method always uses the unscaled version of the image to render the scaled rectangle and performs the required scaling on the fly. It does not use a cached, scaled version of the image for this operation. Scaling of the image from source to destination is performed such that the first coordinate of the source rectangle is mapped to the first coordinate of the destination rectangle, and the second source coordinate is mapped to the second destination coordinate. The subimage is scaled and flipped as needed to preserve those mappings.
Parameters:
img - the specified image to be drawn. This method does nothing if img is null.
dx1 - the x coordinate of the first corner of the destination rectangle.
dy1 - the y coordinate of the first corner of the destination rectangle.
dx2 - the x coordinate of the second corner of the destination rectangle.
dy2 - the y coordinate of the second corner of the destination rectangle.
sx1 - the x coordinate of the first corner of the source rectangle.
sy1 - the y coordinate of the first corner of the source rectangle.
sx2 - the x coordinate of the second corner of the source rectangle.
sy2 - the y coordinate of the second corner of the source rectangle.
observer - object to be notified as more of the image is scaled and converted.
Returns:
false if the image pixels are still changing; true otherwise.
This is better as it takes a few seconds to make a new connection and download image over internet, so if you have one main image that has all the sub images in a big table then total time to download, load and render will be less. extra logic to copy from an area is trivial maybe .1KB of jar file space :)
How to paint multiple icons (ie., say icons between text) in JLabel using graphics? Please do assist me in this effort
One option I would use is to have a sequence of JLabels. Ones with icon and ones with text only.
The other option would be to leverage the mini HTML support of the JLabel: Have
<html><img src='theimage.png'>The text<img src='theimage2.png'>
as the text of the JLabel. This approach works for text formatting but I'm not sure if the image tags work there too.
Or you did override the JLabel.paint() to do custom rendering btw?
Then I would use the following approach:
List<Object> textAndImage = new ArrayList<Object>() {{
add("This ");
add(new ImageIcon("image1.png"));
add(" is ");
add(new ImageIcon("image2.png"));
add(" an ");
add(" imaged text sample ");
}};
FontMetrics fm = g.getFontMetrics();
int x = 0;
for (Object o : textAndImage) {
if (o instanceof String) {
g.drawString((String)o, x, fm.getHeight());
x += fm.stringWidth((String)o);
} else
if (o instanceof ImageIcon) {
((ImageIcon)o).paintIcon(null, g, x, 0);
x += ((ImageIcon)o).getIconWidth();
}
}
Of course this is not a fully fledged solution but might give you some hints how to proceed.
One way is to use a custom border that paints an icon (and text if you want), then you can nest indefinitely.
Here's one such border:
/**
* Show a leading or trailing icon in border.
*/
public static class IconBorder implements Border
{
/**
* #param icon - icon to paint for this border
* #param top outer insets for this border
* #param left
* #param bottom
* #param right
*/
public IconBorder(Icon icon, int top, int left, int bottom, int right)
{
setIcon(icon);
top_ = top;
left_ = left;
bottom_ = bottom;
right_ = right;
rect_ = new Rectangle(0, 0, icon_.getIconWidth(), icon_.getIconHeight());
}
public Insets getBorderInsets(Component c)
{
if( icon_ == null )
return new Insets(0, 0, 0, 0);
return isTrailing_ ? new Insets(top_, left_, bottom_, icon_.getIconWidth() + right_) :
new Insets(top_, icon_.getIconWidth() + left_, bottom_, right_);
}
public boolean isBorderOpaque()
{
return false;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height)
{
if( icon_ != null )
{
if( isTrailing_ )
x = width - icon_.getIconWidth() + 4;
else
x += left_ - 1;
icon_.paintIcon(c, g, x, y + top_);
rect_.x = x;
rect_.y = y;
}
}
public void setIcon(Icon icon)
{
if( icon_ != icon )
icon_ = icon;
}
public Icon getIcon()
{
return icon_;
}
public void setPosition(boolean isTrailing)
{
isTrailing_ = isTrailing;
}
public Rectangle getIconRect()
{
return rect_;
}
private final int top_;
private final int left_;
private final int bottom_;
private final int right_;
private final Rectangle rect_;
private Icon icon_;
private boolean isTrailing_ = true;
}
I use this to add a search icon to a JTextField (a la browser search box). getIconRect can be used to check for mouse hover. For example I change cursor to HAND_CURSOR when mouse is over the search icon.